Учимся на ошибках AI, прокачиваем «S»
Довольно часто использую AI для небольших задач по написанию фрагментов shell-скриптов, и тут выдался случай проверить инструмент в более серьезном деле.
Задача
Нужно реализовать на Go логику контроля доступа для SQL-подобного языка.
ROLE System;
ROLE Admin;
ROLE LocationUser;
ROLE LocationManager;
ROLE Application; -- Projector is executed with this role
GRANT ALL ON ALL TABLES WITH TAG BackofficeTag TO LocationManager;
GRANT INSERT,UPDATE ON ALL TABLES WITH TAG BackofficeTag TO LocationUser;
GRANT SELECT ON TABLE Orders TO LocationUser;
GRANT UPDATE (CloseDatetime, Client) ON TABLE Bill TO LocationUser;
GRANT EXECUTE ON COMMAND Orders TO LocationUser;
GRANT EXECUTE ON QUERY Query1 TO LocationUser;
GRANT EXECUTE ON ALL QUERIES WITH TAG PosTag TO LocationUser;
Главное отклонение от SQL — это метки (tags), благодаря им появляется конструкция:
GRANT INSERT,UPDATE ON ALL TABLES WITH TAG BackofficeTag TO LocationUser;
Попытка номер раз
Подумал — дай набросаю два интерфейса: IACL и IACLBuilder, и пусть «железный человек» их реализует.
package acl
type IACL[QName comparable] interface {
// Returs true if access is granted, false otherwise
CheckTableAccess(op string, fields []string, table QName, roles []QName) bool
// Returs true if access is granted, false otherwise
CheckExecAccess(resource QName, roles []QName) bool
}
type IACLBuilder[QName comparable] interface {
TagTable(tag QName, table QName) IACLBuilder[QName]
// If empty op is used for a given table, all ops will be granted for this table and subsequent grants will be ignored
// If empty fields is used for a given table, op will be granted for all fields subsequent grants will be ignored
GrantOpOnTable(op string, fields []string, table QName, role QName) IACLBuilder[QName]
// Same rules as for GrantOpOnTable, but grants are applied to all tables with given tag, see TagTable method
GrantOpOnTableByTag(op string, fields []string, tag QName, role QName) IACLBuilder[QName]
GrantExec(resource QName, role QName) IACLBuilder[QName]
// Must be the last method to call
Build() IACL[QName]
}
На этом этапе я решил, что контроль EXECUTE это другое и вынес его в отдельные методы.
Сначала я попросил Provide sketch implementation
— с этим всё было хорошо, скомпилировалось.
Затем настал черёд запроса Sketch is ok, provide an implementation of IACL.CheckExecAccess and IACLBuilder.GrantExec, respect comments and keep in mind that I will ask you to implement everything
. И тут сработало, что ж: Provide a testable example for IACL.CheckExecAccess and IACLBuilder.GrantExec implementation
— и здесь исходники компилировались, а пример успешно проходил.
Ну, думаю — успех, Now provide an implementation of IACL.CheckTableAccess, IACLBuilder.TagTable, IACL.GrantOpOnTable and IACL.GrantOpOnTableByTag, respect comments
. Тут начались проблемы на этапе компиляции, после ряда попыток «объяснить» я понял, что быстрее написать самому. Тем более что из задания «железный человек» не осознал, что GrantOpOnTableByTag () надо учитывать в CheckTableAccess (). Разъяснения здесь также не помогли.
Попытка номер два
Анализируя получившийся код и размышляя над путями его реализации, я понял, что логику можно и нужно разделить на два слоя. На одном будет базовая и быстрая проверка доступа к абстрактному ресурсу, а на другом более сложная логика типа «Сначала проверить доступ к таблице целиком, если доступа нет, проверить доступ к таблице по метке, если доступа нет, проверить доступ к конкретным полям». Ну т.е. то самое S из SOLID.
Интерфейс базового слоя упростился до уровня, с которым AI вполне справился:
package acl
type IACL[Role, Operation, Resource comparable] interface {
// HasPermission checks if the specified combination was granted via IACLBuilder.Grant() call.
HasPermission(role Role, o Operation, r Resource) bool
}
type IACLBuilder[Role, Operation, Resource comparable] interface {
Grant(role Role, op Operation, rp Resource)
Build() IACL[Role, Operation, Resource]
}
Выводы
- S, S и ещё раз S