package sandbox // forms_module.go // // Starlark forms module — validates form data against typed form templates. // Gated by the forms.validate extension permission. // // Starlark API: // result = forms.validate(template, data) // # result["valid"] → True/False // # result["errors"] → [{"key": "...", "message": "..."}, ...] import ( "context" "encoding/json" "fmt" "go.starlark.net/starlark" "go.starlark.net/starlarkstruct" "armature/forms" ) // BuildFormsModule creates the "forms" Starlark module. // Requires the forms.validate permission. func BuildFormsModule(_ context.Context) *starlarkstruct.Module { return MakeModule("forms", starlark.StringDict{ "validate": starlark.NewBuiltin("forms.validate", formsValidateBuiltin()), }) } func formsValidateBuiltin() func(*starlark.Thread, *starlark.Builtin, starlark.Tuple, []starlark.Tuple) (starlark.Value, error) { return func(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { var templateVal, dataVal starlark.Value if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 2, &templateVal, &dataVal); err != nil { return nil, err } // Convert template dict → Go map → JSON → TypedFormTemplate templateDict, ok := templateVal.(*starlark.Dict) if !ok { return nil, fmt.Errorf("forms.validate: template must be a dict, got %s", templateVal.Type()) } templateMap := DictToMap(templateDict) templateJSON, err := json.Marshal(templateMap) if err != nil { return nil, fmt.Errorf("forms.validate: failed to marshal template: %w", err) } tpl := forms.ParseTypedFormTemplate(json.RawMessage(templateJSON)) if tpl == nil { return nil, fmt.Errorf("forms.validate: template is invalid or empty") } // Convert data dict → Go map dataDict, ok := dataVal.(*starlark.Dict) if !ok { return nil, fmt.Errorf("forms.validate: data must be a dict, got %s", dataVal.Type()) } dataMap := DictToMap(dataDict) // Coerce to map[string]interface{} data := make(map[string]interface{}, len(dataMap)) for k, v := range dataMap { data[k] = v } // Validate errs := forms.ValidateFormData(tpl, data) // Build result dict errList := make([]starlark.Value, len(errs)) for i, e := range errs { d := starlark.NewDict(2) _ = d.SetKey(starlark.String("key"), starlark.String(e.Key)) _ = d.SetKey(starlark.String("message"), starlark.String(e.Message)) errList[i] = d } result := starlark.NewDict(2) _ = result.SetKey(starlark.String("valid"), starlark.Bool(len(errs) == 0)) _ = result.SetKey(starlark.String("errors"), starlark.NewList(errList)) return result, nil } }