From df4a86b4bb113970a0acafcfe133c163443c5d95 Mon Sep 17 00:00:00 2001 From: Callum Jones Date: Thu, 22 May 2025 20:03:34 +0100 Subject: [PATCH] encode/decode: adds SetElementNamePrefix which allows a prefix to be specified for all element names --- decode.go | 15 ++++++++++----- encode.go | 29 ++++++++++++++++++++--------- encode_test.go | 1 + types.go | 2 +- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/decode.go b/decode.go index 49d2bfd..68c3680 100644 --- a/decode.go +++ b/decode.go @@ -18,6 +18,7 @@ type HTTPDecoder struct { validationStore ValidationStore setValueOnValidationError bool numValidationErrors int + elementNamePrefix string } // NewDecoder creates a new HTTPDecoder. @@ -40,6 +41,10 @@ func (h *HTTPDecoder) SetValidationStore(v ValidationStore) { h.validationStore = v } +func (h *HTTPDecoder) SetElementNamePrefix(prefix string) { + h.elementNamePrefix = prefix +} + // SetValueOnValidationError indicates whether a form value should be set in the form if there was a validation error on that value. func (h *HTTPDecoder) SetValueOnValidationError(b bool) { h.setValueOnValidationError = b @@ -122,7 +127,7 @@ func (h *HTTPDecoder) Decode(data interface{}) error { } func (h *HTTPDecoder) getFormValues(key string) []string { - key = FormElementName(key) + key = FormElementName(h.elementNamePrefix, key) var vals []string @@ -137,7 +142,7 @@ func (h *HTTPDecoder) decode(val reflect.Value, key string, validators []Validat if val.CanInterface() { switch a := val.Interface().(type) { case CustomDecoder: - decodedFormVal, err := a.DecodeFormValue(h.form, key, h.getFormValues(key)) + decodedFormVal, err := a.DecodeFormValue(h.form, FormElementName(h.elementNamePrefix, key), h.getFormValues(key)) if err != nil { return err @@ -162,7 +167,7 @@ func (h *HTTPDecoder) decode(val reflect.Value, key string, validators []Validat return nil case time.Time: - formValue, ok := PopFormValue(h.form, FormElementName(key)) + formValue, ok := PopFormValue(h.form, FormElementName(h.elementNamePrefix, key)) var t time.Time @@ -230,7 +235,7 @@ func (h *HTTPDecoder) decode(val reflect.Value, key string, validators []Validat return nil } - formValue, ok := PopFormValue(h.form, FormElementName(key)) + formValue, ok := PopFormValue(h.form, FormElementName(h.elementNamePrefix, key)) if !ok { // below we are dealing with concrete types that do not call decode recursively. @@ -358,7 +363,7 @@ func (h *HTTPDecoder) passedValidation(key string, value interface{}, validators if !valid { h.numValidationErrors++ - err := h.validationStore.AddValidationError(FormElementName(key), ValidationError{ + err := h.validationStore.AddValidationError(FormElementName(h.elementNamePrefix, key), ValidationError{ Value: value, Error: message, }) diff --git a/encode.go b/encode.go index 3d96d00..94dde63 100644 --- a/encode.go +++ b/encode.go @@ -25,9 +25,10 @@ type HTMLEncoder struct { w io.Writer r *http.Request - decorator Decorator - format bool - validationStore ValidationStore + decorator Decorator + format bool + validationStore ValidationStore + elementNamePrefix string csrfProtection bool } @@ -69,6 +70,11 @@ func (h *HTMLEncoder) SetCSRFProtection(enabled bool) { h.csrfProtection = enabled } +// SetElementNamePrefix allows a prefix to be specified for all element names. +func (h *HTMLEncoder) SetElementNamePrefix(prefix string) { + h.elementNamePrefix = prefix +} + // SetValidationStore can be used to tell the HTMLEncoder about previous validation errors. func (h *HTMLEncoder) SetValidationStore(v ValidationStore) { if v == nil { @@ -78,6 +84,11 @@ func (h *HTMLEncoder) SetValidationStore(v ValidationStore) { h.validationStore = v } +// GetValidationStore returns the current validation store. +func (h *HTMLEncoder) GetValidationStore() ValidationStore { + return h.validationStore +} + func errorIncorrectValue(t reflect.Type) error { return fmt.Errorf("formulate: encode expects a struct value, got: %s", t.String()) } @@ -146,7 +157,7 @@ func (h *HTMLEncoder) recurse(v reflect.Value, key string, field StructField, pa if v.CanInterface() { switch v.Interface().(type) { case time.Time, Select, RadioList, CustomEncoder: - return BuildField(v, FormElementName(key), field, parent, h.decorator, h.ShowConditions) + return BuildField(v, FormElementName(h.elementNamePrefix, key), field, parent, h.decorator, h.ShowConditions) } } @@ -171,7 +182,7 @@ func (h *HTMLEncoder) recurse(v reflect.Value, key string, field StructField, pa nextKey := key + fieldSeparator + v.Type().Field(i).Name - validationErrors, err := h.validationStore.GetValidationErrors(FormElementName(nextKey)) + validationErrors, err := h.validationStore.GetValidationErrors(FormElementName(h.elementNamePrefix, nextKey)) if err != nil { return err @@ -217,7 +228,7 @@ func (h *HTMLEncoder) recurse(v reflect.Value, key string, field StructField, pa return h.recurse(reflect.ValueOf(Raw(buf.Bytes())), key, field, parent) default: - return BuildField(v, FormElementName(key), field, parent, h.decorator, h.ShowConditions) + return BuildField(v, FormElementName(h.elementNamePrefix, key), field, parent, h.decorator, h.ShowConditions) } } @@ -787,14 +798,14 @@ func BuildRadioButtons(r RadioList, key string, field StructField, decorator Dec const fieldSeparator = "." // FormElementName returns the name of the form element within the form, removing the package path and base struct name. -func FormElementName(key string) string { +func FormElementName(prefix string, key string) string { keySplit := strings.Split(key, fieldSeparator) if len(keySplit) > 2 { - return strings.Join(keySplit[2:], fieldSeparator) + return prefix + strings.Join(keySplit[2:], fieldSeparator) } - return key + return prefix + key } func BuildLabel(label string, parent *html.Node, field StructField, decorator Decorator) { diff --git a/encode_test.go b/encode_test.go index d44c67b..f069eb4 100644 --- a/encode_test.go +++ b/encode_test.go @@ -156,6 +156,7 @@ func TestHtmlEncoder_Encode(t *testing.T) { buf := new(bytes.Buffer) m := NewEncoder(buf, nil, nil) m.SetFormat(true) + m.SetElementNamePrefix("EncoderPrefix") if err := m.Encode(&YourDetails{ Name: "Jane Doe", diff --git a/types.go b/types.go index fbd5a61..9b8f989 100644 --- a/types.go +++ b/types.go @@ -93,7 +93,7 @@ type BoolNumber int // DecodeFormValue implements the CustomDecoder interface. func (bn BoolNumber) DecodeFormValue(form url.Values, name string, _ []string) (reflect.Value, error) { - val, _ := PopFormValue(form, FormElementName(name)) + val, _ := PopFormValue(form, name) if val == "on" || val == "1" { return reflect.ValueOf(BoolNumber(1)), nil