Учимся на ошибках AI, прокачиваем «S»

Довольно часто использую AI для небольших задач по написанию фрагментов shell-скриптов, и тут выдался случай проверить инструмент в более серьезном деле.
gtksufgltkehxvy5moavdu_3htm.png


Задача

Нужно реализовать на 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

© Habrahabr.ru