siyuan-note/siyuan · error

unsupported enum field

Error message

unsupported enum field: %s

What it means

A field carrying an `enum=a|b|c` api option must be of string kind; if the contract author applies enum to a non-string field (int, bool, struct), decodeRequestFields returns this error before any value checking. It is a contract-definition mistake surfaced at request time.

Solutions

  1. Change the contract field to type string, or express the enum via `const=` options per accepted value
  2. Use a plain string field with enum for textual enums
  3. Add a unit test decoding a sample request for every contract to catch kind mismatches early

Example fix

// before
type Req struct { Level int `json:"level" api:"enum=1|2|3"` }
// after
type Req struct { Level string `json:"level" api:"enum=1|2|3"` }
Defensive patterns

Strategy: validation

Validate before calling

func validateEnumKinds(t reflect.Type) error { for i := 0; i < t.NumField(); i++ { f := t.Field(i); if strings.Contains(f.Tag.Get("api"), "enum=") && f.Type.Kind() != reflect.String { return fmt.Errorf("enum field %s must be string kind", f.Name) } }; return nil }

Type guard

null

Try / catch

if err := decodeRequestFields(value, fields); err != nil { if strings.HasPrefix(err.Error(), "unsupported enum field") { return fmt.Errorf("contract bug: enum tag on non-string field: %w", err) }; return err }

Prevention

When it happens

Trigger: Declaring `api:"enum=1|2|3"` on an int field, or enum on a custom string-like type that reflection sees as non-string kind, then processing a request for that contract.

Common situations: Copy-pasting enum tags from string fields onto numeric fields; using named non-string types (type Mode int) with enum tags; code-generated contracts adding enum to wrong fields.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/c39e0553c2f86bba. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/decode.go:126

			decodeErr = decodeRequestValue(raw, value.Field(i))
		}
		if decodeErr != nil {
			if has("ignoretype") {
				continue
			}
			return fmt.Errorf("Field [%s] has an invalid type: %w", name, decodeErr)
		}
		if has("trim") {
			trimmed := strings.TrimSpace(value.Field(i).String())
			if trimmed == "" {
				return fmt.Errorf("Field [%s] must not be empty", name)
			}
			value.Field(i).SetString(trimmed)
		}
		for _, option := range strings.Split(field.Tag.Get("api"), ",") {
			if strings.HasPrefix(option, "enum=") {
				if field.Type.Kind() != reflect.String {
					return fmt.Errorf("unsupported enum field: %s", name)
				}
				found := false
				for _, choice := range strings.Split(strings.TrimPrefix(option, "enum="), "|") {
					if value.Field(i).String() == choice {
						found = true
						break
					}
				}
				if !found {
					return fmt.Errorf("Field [%s] has an invalid value", name)
				}
			}
			if strings.HasPrefix(option, "const=") {
				var expected, actual any
				if err := json.Unmarshal([]byte(strings.TrimPrefix(option, "const=")), &expected); err != nil {
					return err
				}
				data, err := json.Marshal(value.Field(i).Interface())

View on GitHub (pinned to 9f775e8a12)