param.go

  1package param
  2
  3import (
  4	"encoding/json"
  5	"reflect"
  6)
  7
  8// NullStruct is used to set a struct to the JSON value null.
  9// Check for null structs with [IsNull].
 10//
 11// Only the first type parameter should be provided,
 12// the type PtrT will be inferred.
 13//
 14//	json.Marshal(param.NullStruct[MyStruct]()) -> 'null'
 15//
 16// To send null to an [Opt] field use [Null].
 17func NullStruct[T ParamStruct, PtrT InferPtr[T]]() T {
 18	var t T
 19	pt := PtrT(&t)
 20	pt.setMetadata(nil)
 21	return *pt
 22}
 23
 24// Override replaces the value of a struct with any type.
 25//
 26// Only the first type parameter should be provided,
 27// the type PtrT will be inferred.
 28//
 29// It's often useful for providing raw JSON
 30//
 31//	param.Override[MyStruct](json.RawMessage(`{"foo": "bar"}`))
 32//
 33// The public fields of the returned struct T will be unset.
 34//
 35// To override a specific field in a struct, use its [SetExtraFields] method.
 36func Override[T ParamStruct, PtrT InferPtr[T]](v any) T {
 37	var t T
 38	pt := PtrT(&t)
 39	pt.setMetadata(v)
 40	return *pt
 41}
 42
 43// IsOmitted returns true if v is the zero value of its type.
 44//
 45// If IsOmitted is true, and the field uses a `json:"...,omitzero"` tag,
 46// the field will be omitted from the request.
 47//
 48// If v is set explicitly to the JSON value "null", IsOmitted returns false.
 49func IsOmitted(v any) bool {
 50	if v == nil {
 51		return false
 52	}
 53	if o, ok := v.(Optional); ok {
 54		return o.isZero()
 55	}
 56	return reflect.ValueOf(v).IsZero()
 57}
 58
 59// IsNull returns true if v was set to the JSON value null.
 60//
 61// To set a param to null use [NullStruct] or [Null]
 62// depending on the type of v.
 63//
 64// IsNull returns false if the value is omitted.
 65func IsNull(v ParamNullable) bool {
 66	return v.null()
 67}
 68
 69// ParamNullable encapsulates all structs in parameters,
 70// and all [Opt] types in parameters.
 71type ParamNullable interface {
 72	null() bool
 73}
 74
 75// ParamStruct represents the set of all structs that are
 76// used in API parameters, by convention these usually end in
 77// "Params" or "Param".
 78type ParamStruct interface {
 79	Overrides() (any, bool)
 80	null() bool
 81	extraFields() map[string]any
 82}
 83
 84// This is an implementation detail and should never be explicitly set.
 85type InferPtr[T ParamStruct] interface {
 86	setMetadata(any)
 87	*T
 88}
 89
 90// APIObject should be embedded in api object fields, preferably using an alias to make private
 91type APIObject struct{ metadata }
 92
 93// APIUnion should be embedded in all api unions fields, preferably using an alias to make private
 94type APIUnion struct{ metadata }
 95
 96// Overrides returns the value of the struct when it is created with
 97// [Override], the second argument helps differentiate an explicit null.
 98func (m metadata) Overrides() (any, bool) {
 99	if _, ok := m.any.(metadataExtraFields); ok {
100		return nil, false
101	}
102	return m.any, m.any != nil
103}
104
105// ExtraFields returns the extra fields added to the JSON object.
106func (m metadata) ExtraFields() map[string]any {
107	if extras, ok := m.any.(metadataExtraFields); ok {
108		return extras
109	}
110	return nil
111}
112
113// Omit can be used with [metadata.SetExtraFields] to ensure that a
114// required field is omitted. This is useful as an escape hatch for
115// when a required is unwanted for some unexpected reason.
116const Omit forceOmit = -1
117
118// SetExtraFields adds extra fields to the JSON object.
119//
120// SetExtraFields will override any existing fields with the same key.
121// For security reasons, ensure this is only used with trusted input data.
122//
123// To intentionally omit a required field, use [Omit].
124//
125//	foo.SetExtraFields(map[string]any{"bar": Omit})
126//
127// If the struct already contains the field ExtraFields, then this
128// method will have no effect.
129func (m *metadata) SetExtraFields(extraFields map[string]any) {
130	m.any = metadataExtraFields(extraFields)
131}
132
133// extraFields aliases [metadata.ExtraFields] to avoid name collisions.
134func (m metadata) extraFields() map[string]any { return m.ExtraFields() }
135
136func (m metadata) null() bool {
137	if _, ok := m.any.(metadataNull); ok {
138		return true
139	}
140
141	if msg, ok := m.any.(json.RawMessage); ok {
142		return string(msg) == "null"
143	}
144
145	return false
146}
147
148type metadata struct{ any }
149type metadataNull struct{}
150type metadataExtraFields map[string]any
151
152func (m *metadata) setMetadata(override any) {
153	if override == nil {
154		m.any = metadataNull{}
155		return
156	}
157	m.any = override
158}