feat(12.1-02): password and form-only fields, rules per operation and preset
- pact.FormVirtualFields lists form fields that are not model columns: never bound, filled or projected; their values reach the Form hooks through cabana.VirtualFieldsFromContext when the field's context allows the operation - type: password is a masked field that must be listed as virtual - pact.FormRules supplies the rule set per operation and replaces the model's Rules() for admin saves; a rule on a virtual field sees the submitted value - preset on a text field follows another text field on the create form - SPA: PasswordField, preset handling in FormView, empty password left out of an update - README, docs, OpenAPI document, TS types and dist updated
This commit is contained in:
@@ -113,6 +113,12 @@ func compileExtension(pluginID string, cc *CompiledController, fsys fs.FS) error
|
||||
for _, field := range cc.Form.Fields {
|
||||
fields[field.Name] = field
|
||||
}
|
||||
if err := checkVirtualFields(cc, fields); err != nil {
|
||||
return bootErr(pluginID, id, file, err)
|
||||
}
|
||||
if err := checkPresets(cc.Form.Fields); err != nil {
|
||||
return bootErr(pluginID, id, file, err)
|
||||
}
|
||||
writable := map[string]bool{}
|
||||
for _, field := range cc.Writable {
|
||||
writable[field.Name] = true
|
||||
@@ -147,6 +153,44 @@ func compileExtension(pluginID string, cc *CompiledController, fsys fs.FS) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// virtualFieldTypes are the field types a controller may list through
|
||||
// pact.FormVirtualFields: the value is one scalar.
|
||||
var virtualFieldTypes = map[string]bool{
|
||||
"password": true, "text": true, "textarea": true, "number": true,
|
||||
"checkbox": true, "switch": true, "dropdown": true,
|
||||
}
|
||||
|
||||
// checkVirtualFields checks the controller's pact.FormVirtualFields list
|
||||
// against the form: every `type: password` field must be listed (its value is
|
||||
// never a model column), and every listed name must be a field of the form
|
||||
// with a scalar type.
|
||||
func checkVirtualFields(cc *CompiledController, fields map[string]FormField) error {
|
||||
for _, field := range cc.Form.Fields {
|
||||
if field.Type == "password" && !cc.virtual[field.Name] {
|
||||
return fmt.Errorf("field %s: type password needs the controller to list it in FormVirtualFields", field.Name)
|
||||
}
|
||||
}
|
||||
src, ok := cc.Controller.(pact.FormVirtualFields)
|
||||
if !ok || src == nil {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, name := range src.FormVirtualFields() {
|
||||
if seen[name] {
|
||||
return fmt.Errorf("FormVirtualFields lists field %s twice", name)
|
||||
}
|
||||
seen[name] = true
|
||||
field, exists := fields[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("FormVirtualFields: field %s is not a field of this form", name)
|
||||
}
|
||||
if !virtualFieldTypes[field.Type] {
|
||||
return fmt.Errorf("FormVirtualFields: field %s has type %s (want password, text, textarea, number, checkbox, switch or dropdown)", name, field.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compilePartials reads and parses every partial the controller declares:
|
||||
// config_list.yaml headerPartial, config_form.yaml preview.headerPartial and
|
||||
// each `type: partial` field's path, all resolving to {ConfigDir}/_{name}.htm.
|
||||
|
||||
Reference in New Issue
Block a user