1package param
2
3import (
4 "encoding/json"
5 "reflect"
6)
7
8// NullObj is used to mark a struct as null.
9// To send null to an [Opt] field use [NullOpt].
10func NullObj[T NullableObject, PT Settable[T]]() T {
11 var t T
12 pt := PT(&t)
13 pt.setMetadata(nil)
14 return *pt
15}
16
17// To override a specific field in a struct, use its [WithExtraFields] method.
18func OverrideObj[T OverridableObject, PT Settable[T]](v any) T {
19 var t T
20 pt := PT(&t)
21 pt.setMetadata(nil)
22 return *pt
23}
24
25// IsOmitted returns true if v is the zero value of its type.
26//
27// It indicates if a field with the `json:"...,omitzero"` tag will be omitted
28// from serialization.
29//
30// If v is set explicitly to the JSON value "null", this function will return false.
31// Therefore, when available, prefer using the [IsPresent] method to check whether
32// a field is present.
33//
34// Generally, this function should only be used on structs, arrays, maps.
35func IsOmitted(v any) bool {
36 if v == nil {
37 return false
38 }
39 if o, ok := v.(interface{ IsOmitted() bool }); ok {
40 return o.IsOmitted()
41 }
42 return reflect.ValueOf(v).IsZero()
43}
44
45type NullableObject = overridableStruct
46type OverridableObject = overridableStruct
47
48type Settable[T overridableStruct] interface {
49 setMetadata(any)
50 *T
51}
52
53type overridableStruct interface {
54 IsNull() bool
55 IsOverridden() (any, bool)
56 GetExtraFields() map[string]any
57}
58
59// APIObject should be embedded in api object fields, preferably using an alias to make private
60type APIObject struct{ metadata }
61
62// APIUnion should be embedded in all api unions fields, preferably using an alias to make private
63type APIUnion struct{ metadata }
64
65type metadata struct{ any }
66type metadataNull struct{}
67type metadataExtraFields map[string]any
68
69// IsNull returns true if the field is the explicit value `null`,
70// prefer using [IsPresent] to check for presence, since it checks against null and omitted.
71func (m metadata) IsNull() bool {
72 if _, ok := m.any.(metadataNull); ok {
73 return true
74 }
75
76 if msg, ok := m.any.(json.RawMessage); ok {
77 return string(msg) == "null"
78 }
79
80 return false
81}
82
83func (m metadata) IsOverridden() (any, bool) {
84 if _, ok := m.any.(metadataExtraFields); ok {
85 return nil, false
86 }
87 return m.any, m.any != nil
88}
89
90func (m metadata) GetExtraFields() map[string]any {
91 if extras, ok := m.any.(metadataExtraFields); ok {
92 return extras
93 }
94 return nil
95}
96
97func (m *metadata) WithExtraFields(fields map[string]any) {
98 m.any = metadataExtraFields(fields)
99}
100
101func (m *metadata) setMetadata(override any) {
102 if override == nil {
103 m.any = metadataNull{}
104 return
105 }
106 m.any = override
107}