001package gudusoft.gsqlparser.demos.sqlguard;
002
003import gudusoft.gsqlparser.util.json.JSON;
004
005import java.util.LinkedHashMap;
006import java.util.Map;
007
008/** Request POJO for the lightweight LLM SQL Guard worker. */
009@SuppressWarnings("unchecked")
010public class SqlGuardRequest {
011    public String requestId;
012    public String dialect = "postgresql";
013    public String sql;
014    public String catalogMode = "sample";
015    public String sampleCatalogId = "ecommerce";
016    public String catalog;
017    public String policyPreset = "pii-basic";
018    /**
019     * Optional natural-language question that motivated the SQL — the
020     * caller's stated intent. Consumed by blueprint §S23
021     * ({@code AGGREGATION_GRAIN_MISMATCH}) to compare against the SQL's
022     * actual aggregation grain. Deliberately kept as a free-text string;
023     * the rule uses a small keyword whitelist, not NLP. {@code null}
024     * disables every intent-vs-SQL check (existing requests round-trip
025     * byte-identical).
026     */
027    public String question;
028    public Map<String, Object> userContext = new LinkedHashMap<String, Object>();
029    public Map<String, Object> options = new LinkedHashMap<String, Object>();
030
031    public static SqlGuardRequest fromJson(String json) {
032        Object parsed = JSON.parseObject(json);
033        if (!(parsed instanceof Map)) {
034            throw new IllegalArgumentException("request body must be a JSON object");
035        }
036        Map<String, Object> m = (Map<String, Object>) parsed;
037        SqlGuardRequest r = new SqlGuardRequest();
038        r.requestId = optionalString(m, "requestId");
039        String dialect = optionalString(m, "dialect");
040        if (dialect != null) r.dialect = dialect;
041        r.sql = optionalString(m, "sql");
042        String catalogMode = optionalString(m, "catalogMode");
043        if (catalogMode != null) r.catalogMode = catalogMode;
044        String sampleCatalogId = optionalString(m, "sampleCatalogId");
045        if (sampleCatalogId != null) r.sampleCatalogId = sampleCatalogId;
046        r.catalog = optionalString(m, "catalog");
047        String policyPreset = optionalString(m, "policyPreset");
048        if (policyPreset != null) r.policyPreset = policyPreset;
049        r.question = optionalString(m, "question");
050        Object uc = m.get("userContext");
051        if (uc instanceof Map) r.userContext.putAll((Map<String, Object>) uc);
052        else if (uc != null) throw new IllegalArgumentException("userContext must be a JSON object");
053        Object opt = m.get("options");
054        if (opt instanceof Map) r.options.putAll((Map<String, Object>) opt);
055        else if (opt != null) throw new IllegalArgumentException("options must be a JSON object");
056        return r;
057    }
058
059    private static String optionalString(Map<String, Object> m, String key) {
060        Object o = m.get(key);
061        if (o == null) {
062            return null;
063        }
064        if (!(o instanceof String)) {
065            throw new IllegalArgumentException(key + " must be a string");
066        }
067        return (String) o;
068    }
069}