feat: smart form validation with field types and HTML5 attrs#255
feat: smart form validation with field types and HTML5 attrs#255
Conversation
…ssword confirmation Add specialized field types (email, url, phone/tel, password) that generate appropriate Go validation tags and HTML5 input attributes. Replace template-level GoType branching with metadata-driven ValidateTag, eliminating duplicated logic across 5 handler templates. Add password confirmation with eqfield cross-validation. Enhance ValidationToMultiError in the livetemplate framework with formatFieldName for human-readable error messages and toSnakeCase for correct multi-word form field name matching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewOverall this is a well-structured refactor with a clean design. The 🔴 Security: Passwords stored as plain textThe generated handler stores the password value directly into the database with no hashing. The At minimum, the generated handler should hash the password before persistence (e.g.,
🟡 Bug:
|
There was a problem hiding this comment.
Pull request overview
This PR enhances lvt gen resource code generation by introducing metadata-driven field typing for smarter validation tags and HTML5 input attributes (e.g., email, url, tel, password), and by centralizing type→metadata mapping to reduce duplicated template logic.
Changes:
- Add
FieldMetadataand a unifiedfieldTypeTableto drive Go/SQL mapping, validation tags, and HTML input attributes from one place. - Update resource handler/form templates (generator + system kits) to use metadata (
ValidateTag,HTMLInputType, min lengths) and add password confirmation fields. - Expand parser/generator tests and update golden outputs accordingly.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| testdata/golden/resource_template.tmpl.golden | Updates golden template output to include new HTML attributes (e.g., minlength). |
| internal/parser/fields.go | Introduces FieldMetadata + centralized fieldTypeTable; wires metadata into ParseFields. |
| internal/parser/fields_test.go | Adds coverage for new field types and metadata propagation through ParseFields. |
| internal/generator/types.go | Embeds FieldMetadata into FieldData so templates can access validation/HTML metadata. |
| internal/generator/types_test.go | Adds a test ensuring generator FieldData correctly carries metadata. |
| internal/generator/templates/resource/handler.go.tmpl | Switches handler struct validation tags to metadata-driven ValidateTag; adds password confirmation fields. |
| internal/generator/templates/components/form.tmpl | Uses metadata-driven HTML input attributes/types; renders password confirmation fields. |
| internal/kits/system/multi/templates/resource/handler.go.tmpl | Mirrors metadata-driven validation + password confirmation in multi kit handler template. |
| internal/kits/system/multi/templates/resource/embedded_handler.go.tmpl | Mirrors metadata-driven validation + password confirmation for embedded resources (multi kit). |
| internal/kits/system/multi/templates/resource/embedded_template.tmpl.tmpl | Uses metadata-driven HTML input types/attrs; adds password confirmation UI (multi kit). |
| internal/kits/system/multi/components/form.tmpl | Uses metadata-driven HTML input types/attrs; adds password confirmation UI (multi kit). |
| internal/kits/system/single/templates/resource/handler.go.tmpl | Mirrors metadata-driven validation + password confirmation in single kit handler template. |
| internal/kits/system/single/templates/resource/embedded_handler.go.tmpl | Mirrors metadata-driven validation + password confirmation for embedded resources (single kit). |
| internal/kits/system/single/templates/resource/embedded_template.tmpl.tmpl | Uses metadata-driven HTML input types/attrs; adds password confirmation UI (single kit). |
| internal/kits/system/single/components/form.tmpl | Uses metadata-driven HTML input types/attrs; adds password confirmation UI (single kit). |
| golden_test.go | Updates golden tests to supply Field.Metadata so generated templates include new HTML/validation output. |
| type AddInput struct { | ||
| [[- range .NonReferenceFields]] | ||
| [[- if eq .GoType "bool"]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]"` | ||
| [[- else if .IsSelect]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required"` | ||
| [[- else if eq .GoType "string"]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required,min=1"` | ||
| [[- if .ValidateTag]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"[[.ValidateTag]]"` | ||
| [[- else]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required"` | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]"` | ||
| [[- end]] |
There was a problem hiding this comment.
This template now uses .ValidateTag for all non-reference fields, which changes the embedded resource string validation behavior from min=1 (previous logic) to whatever the global metadata table returns (currently min=3 for string/text). If embedded resources intentionally allowed 1-character strings, this is a breaking change in generated code. Suggestion: either preserve the prior min=1 behavior for embedded handlers (template override) or introduce a separate/parameterized metadata policy for embedded generation.
| func TestParseFieldsMetadata(t *testing.T) { | ||
| fields, err := ParseFields([]string{"email:email", "secret:password", "website:url", "phone:tel"}) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| if len(fields) != 4 { | ||
| t.Fatalf("expected 4 fields, got %d", len(fields)) | ||
| } | ||
|
|
||
| // email field | ||
| if fields[0].Metadata.ValidateTag != "required,email" { | ||
| t.Errorf("email ValidateTag = %q, want %q", fields[0].Metadata.ValidateTag, "required,email") | ||
| } | ||
| if fields[0].Metadata.HTMLInputType != "email" { | ||
| t.Errorf("email HTMLInputType = %q, want %q", fields[0].Metadata.HTMLInputType, "email") | ||
| } | ||
|
|
||
| // password field | ||
| if fields[1].Metadata.ValidateTag != "required,min=8" { | ||
| t.Errorf("password ValidateTag = %q, want %q", fields[1].Metadata.ValidateTag, "required,min=8") | ||
| } | ||
| if !fields[1].Metadata.IsPassword { | ||
| t.Error("password field should have IsPassword=true") | ||
| } | ||
| if fields[1].Metadata.HTMLMinLength != 8 { | ||
| t.Errorf("password HTMLMinLength = %d, want 8", fields[1].Metadata.HTMLMinLength) | ||
| } | ||
|
|
||
| // url field | ||
| if fields[2].Metadata.ValidateTag != "required,url" { | ||
| t.Errorf("url ValidateTag = %q, want %q", fields[2].Metadata.ValidateTag, "required,url") | ||
| } | ||
|
|
||
| // tel field | ||
| if fields[3].Metadata.HTMLInputType != "tel" { | ||
| t.Errorf("tel HTMLInputType = %q, want %q", fields[3].Metadata.HTMLInputType, "tel") | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
New metadata/validation behavior isn't exercised for references:... field types (and currently ParseFields would give them default metadata). Adding a table-driven test case for a reference field (e.g. user_id:references:users) would help ensure reference IDs get an appropriate ValidateTag (typically required) and prevent regressions in generated handler validation.
| type AddInput struct { | ||
| [[- range .NonReferenceFields]] | ||
| [[- if eq .GoType "bool"]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]"` | ||
| [[- else if .IsSelect]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required"` | ||
| [[- else if eq .GoType "string"]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required,min=1"` | ||
| [[- if .ValidateTag]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"[[.ValidateTag]]"` | ||
| [[- else]] | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]" validate:"required"` | ||
| [[.Name | camelCase]] [[.GoType]] `json:"[[.Name]]"` | ||
| [[- end]] |
There was a problem hiding this comment.
This template now uses .ValidateTag for all non-reference fields, which changes the embedded resource string validation behavior from min=1 (previous logic) to whatever the global metadata table returns (currently min=3 for string/text). If embedded resources intentionally allowed 1-character strings, this is a breaking change in generated code. Suggestion: either preserve the prior min=1 behavior for embedded handlers (template override) or introduce a separate/parameterized metadata policy for embedded generation.
| GoType: goType, | ||
| SQLType: sqlType, | ||
| IsTextarea: isTextarea, | ||
| Metadata: GetFieldMetadata(typ, goType), | ||
| } |
There was a problem hiding this comment.
ParseFields sets Metadata using GetFieldMetadata(typ, goType), but for reference fields (references:table), typ is just "references" so metadata falls back to defaults (empty ValidateTag). This causes generated AddInput/UpdateInput structs to omit server-side validation for reference IDs, even though migrations make these columns NOT NULL and FK-constrained—empty IDs will slip through until a DB constraint error. Suggestion: detect references: types and assign at least ValidateTag:"required" (and appropriate HTMLInputType), or pass/normalize fullType into GetFieldMetadata and have it handle references: prefixes explicitly.
| if !ok { | ||
| return "", "", false, fmt.Errorf("unsupported type '%s' (supported: string, text, int, bool, float, time, email, url, phone, password, references:table)", typ) | ||
| } |
There was a problem hiding this comment.
MapType's unsupported-type error message lists supported types but omits some values that are accepted (e.g. tel, textarea, longtext, datetime, timestamp). This can confuse users when they mistype a field type. Suggestion: derive the supported-types list from fieldTypeTable keys (plus references:...) or update the string to include the full set.
- Remove unused goType param from GetFieldMetadata (Claude) - Add required validate tag for reference field IDs (Copilot) - Add password confirmation to embedded template edit forms (Claude) - Add maxlength to embedded template password edit inputs (consistency) - Derive supported-types list via supportedTypes() helper (Copilot) - Add unknown_type fallback test case for GetFieldMetadata (Claude) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
PR Review - feat: smart form validation Overall: clean refactor. The fieldTypeTable unification and embedded FieldMetadata are well executed. Test coverage is good. A few issues: FUNCTIONAL ISSUE: UpdateInput always requires re-entering passwords. In all handler templates, UpdateInput has the same confirmation field as AddInput with validate required+eqfield. This forces users to re-enter their password on any update, even unrelated fields. Consider omitempty on updates. SECURITY: Generated code stores passwords as plain text. No hashing step is generated. A TODO comment in the generated handler for password fields would prevent silent plain-text storage. MINOR - supportedTypes() can drift: The hardcoded string in supportedTypes() will fall out of sync with fieldTypeTable as types are added. Consider deriving from the table keys or adding a cross-check test. MINOR - phone/tel has no format validation: Unlike email and url types, phone only gets validate required with no format check. Worth documenting so users know type=tel is browser-hints only. MINOR - Select field metadata: HTMLInputType is set to text for select fields even though they render as select elements. Harmless but misleading. WHAT IS GOOD: fieldTypeTable is an excellent refactor eliminating parallel-switch maintenance. Embedded FieldMetadata removes manual copying. Password confirmation with eqfield is a great UX. Table-driven tests are thorough. Golden tests updated and passing. Generated with Claude Code |
The CLI's parseFieldsWithInference constructed parser.Field without Metadata, causing generated handlers to omit validation tags entirely. This broke E2E validation tests since server-side validation was no longer enforced. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewOverall this is a clean, well-thought-out refactor. The Security: Generated handler stores passwords as plain textThe Suggested remedies (pick one):
UpdateInput forces password re-entry on every updateIn all handler templates, SecretConfirmation string `json:"secret_confirmation" validate:"required,eqfield=Secret"`This means any update to any other field on the resource (e.g. changing a name) requires the user to also re-enter their password and confirmation. Consider omitting the password/confirmation from
|
Summary
email,url,phone/tel,password) with smart validation tags and HTML5 input attributesValidateTagvia unifiedfieldTypeTable, eliminating duplicated logic across 5 handler templateseqfieldcross-field validation and dedicated form inputsMapTypeandGetFieldMetadatainto a single lookup table (no more parallel switches to keep in sync)FieldMetadatainFieldDatato eliminate manual field-by-field copyDepends on
ValidationToMultiErrorenhancements: https://github.com/livetemplate/livetemplate/pull/new/feat/enhanced-validation-errorsWhat it looks like
Generates:
validate:"required,email",validate:"required,url",validate:"required,min=8"tagstype="email",type="tel",type="password",minlength="8"attributeseqfieldcross-validationTest plan
TestMapType— new field types (email, url, phone, tel, password) parse correctlyTestGetFieldMetadata— table-driven test for all type→metadata mappingsTestParseFieldsMetadata— metadata flows through ParseFieldsTestFieldDataFromFieldsCopiesMetadata— embedded FieldMetadata propagates to FieldDatago test ./...green (both repos)🤖 Generated with Claude Code