a0774f4f1a
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
package alertsource
|
|
|
|
import "time"
|
|
|
|
// FieldKind is the declared type of a configuration field.
|
|
type FieldKind int
|
|
|
|
// The four declarable field kinds.
|
|
const (
|
|
FieldKindString FieldKind = iota
|
|
FieldKindInt
|
|
FieldKindBool
|
|
FieldKindDuration
|
|
)
|
|
|
|
// jsonTypeString is the JSON Schema "type" value shared by FieldKindString
|
|
// and FieldKindDuration, and the x-kandev-field-kind tag for FieldKindString.
|
|
const jsonTypeString = "string"
|
|
|
|
// tag is the x-kandev-field-kind value for the kind (A3). It matches the
|
|
// constructor's kind name: NewIntField -> "int". It deliberately differs
|
|
// from jsonType for Int (whose JSON Schema "type" is "integer") and for
|
|
// Duration (whose JSON Schema "type" is "string") — the extension names the
|
|
// DECLARED kind, "type" names the JSON shape.
|
|
func (k FieldKind) tag() string {
|
|
switch k {
|
|
case FieldKindString:
|
|
return jsonTypeString
|
|
case FieldKindInt:
|
|
return "int"
|
|
case FieldKindBool:
|
|
return "bool"
|
|
case FieldKindDuration:
|
|
return "duration"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// jsonType is the JSON Schema "type" keyword for the kind (D9/A3).
|
|
func (k FieldKind) jsonType() string {
|
|
switch k {
|
|
case FieldKindString, FieldKindDuration:
|
|
return jsonTypeString
|
|
case FieldKindInt:
|
|
return "integer"
|
|
case FieldKindBool:
|
|
return "boolean"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Field is a single configuration field declaration, built through one of
|
|
// the NewXField constructors and its chained modifiers. Fields are REQUIRED
|
|
// by default; Optional() or Default() relaxes that.
|
|
//
|
|
// A *Field is a construction-time-only handle: FieldSpec.Field copies *f by
|
|
// value into the spec, so a pointer obtained from a constructor stops
|
|
// aliasing anything inside the spec once it has been passed there. Mutating
|
|
// a builder pointer after calling FieldSpec.Field on it, or sharing one
|
|
// concurrently with any other method, is undefined.
|
|
type Field struct {
|
|
name string
|
|
kind FieldKind
|
|
secret bool
|
|
optional bool
|
|
advanced bool
|
|
hasDefault bool
|
|
defaultVal any
|
|
hasExample bool
|
|
exampleVal any
|
|
description string
|
|
}
|
|
|
|
func newField(name string, kind FieldKind) *Field {
|
|
return &Field{name: name, kind: kind}
|
|
}
|
|
|
|
// NewStringField declares a string-valued field.
|
|
func NewStringField(name string) *Field { return newField(name, FieldKindString) }
|
|
|
|
// NewIntField declares an integer-valued field.
|
|
func NewIntField(name string) *Field { return newField(name, FieldKindInt) }
|
|
|
|
// NewBoolField declares a boolean-valued field.
|
|
func NewBoolField(name string) *Field { return newField(name, FieldKindBool) }
|
|
|
|
// NewDurationField declares a Go-syntax duration field (e.g. "30s", "5m").
|
|
func NewDurationField(name string) *Field { return newField(name, FieldKindDuration) }
|
|
|
|
// Secret marks the field as sensitive: its value is stored through the
|
|
// shared secret store and never written into config_json or emitted
|
|
// configuration. Legal only on a string field — declaring it on any other
|
|
// kind is a declaration error surfaced by FieldSpec.Validate(), not here,
|
|
// because a chained builder cannot itself return an error mid-chain.
|
|
func (f *Field) Secret() *Field {
|
|
f.secret = true
|
|
return f
|
|
}
|
|
|
|
// Optional marks the field as not required. Idempotent.
|
|
func (f *Field) Optional() *Field {
|
|
f.optional = true
|
|
return f
|
|
}
|
|
|
|
// Advanced marks the field as advanced (surfaced by x-kandev-advanced in the
|
|
// emitted schema, for a collapsed section in the rendered form). Idempotent.
|
|
func (f *Field) Advanced() *Field {
|
|
f.advanced = true
|
|
return f
|
|
}
|
|
|
|
// Description sets the field's human-readable description. Last-write-wins.
|
|
func (f *Field) Description(s string) *Field {
|
|
f.description = s
|
|
return f
|
|
}
|
|
|
|
// Default sets the field's default value and implies Optional. The value
|
|
// must match the field's kind (string, int, bool, time.Duration); a
|
|
// mismatch, and declaring a default on a Secret field, are both declaration
|
|
// errors surfaced by FieldSpec.Validate(). Last-write-wins.
|
|
func (f *Field) Default(v any) *Field {
|
|
f.defaultVal = v
|
|
f.hasDefault = true
|
|
f.optional = true
|
|
return f
|
|
}
|
|
|
|
// Example sets the field's example value, shown in the emitted schema and
|
|
// documentation. The value must match the field's kind; declaring an
|
|
// example on a Secret field is a declaration error surfaced by
|
|
// FieldSpec.Validate(). Last-write-wins.
|
|
func (f *Field) Example(v any) *Field {
|
|
f.exampleVal = v
|
|
f.hasExample = true
|
|
return f
|
|
}
|
|
|
|
// kindMatches reports whether v is the Go value type the kind declares.
|
|
func kindMatches(kind FieldKind, v any) bool {
|
|
switch kind {
|
|
case FieldKindString:
|
|
_, ok := v.(string)
|
|
return ok
|
|
case FieldKindInt:
|
|
_, ok := v.(int)
|
|
return ok
|
|
case FieldKindBool:
|
|
_, ok := v.(bool)
|
|
return ok
|
|
case FieldKindDuration:
|
|
_, ok := v.(time.Duration)
|
|
return ok
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// render turns a declared Go value into its portable (JSON-safe) form. It is
|
|
// the single per-kind function D4/D9 require: identity for
|
|
// String/Int/Bool, and time.Duration.String() for Duration — used both when
|
|
// emitting a schema's default/examples and when marshalling a resolved
|
|
// Duration value into config_json, so the two call sites cannot diverge.
|
|
func render(kind FieldKind, v any) any {
|
|
if kind == FieldKindDuration {
|
|
if d, ok := v.(time.Duration); ok {
|
|
return d.String()
|
|
}
|
|
}
|
|
return v
|
|
}
|