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