Files
2026-01-27 23:32:58 +00:00

187 lines
5.0 KiB
Go

package param
import (
"encoding/json"
"github.com/openai/openai-go/v3/internal/encoding/json/sentinel"
"reflect"
)
// NullStruct is used to set a struct to the JSON value null.
// Check for null structs with [IsNull].
//
// Only the first type parameter should be provided,
// the type PtrT will be inferred.
//
// json.Marshal(param.NullStruct[MyStruct]()) -> 'null'
//
// To send null to an [Opt] field use [Null].
func NullStruct[T ParamStruct, PtrT InferPtr[T]]() T {
var t T
pt := PtrT(&t)
pt.setMetadata(nil)
return *pt
}
// Override replaces the value of a struct with any type.
//
// Only the first type parameter should be provided,
// the type PtrT will be inferred.
//
// It's often useful for providing raw JSON
//
// param.Override[MyStruct](json.RawMessage(`{"foo": "bar"}`))
//
// The public fields of the returned struct T will be unset.
//
// To override a specific field in a struct, use its [SetExtraFields] method.
func Override[T ParamStruct, PtrT InferPtr[T]](v any) T {
var t T
pt := PtrT(&t)
pt.setMetadata(v)
return *pt
}
// SetJSON configures a param struct to serialize with the provided raw JSON data.
// Use this when you have existing JSON that you want to send as request parameters.
//
// var req example.NewUserParams
// var rawJSON = []byte(`{"name": "...", "age": 40}`)
// param.SetJSON(rawJSON, &req)
// res, err := client.Users.New(ctx, req)
//
// Note: The struct's existing fields will be ignored; only the provided JSON is serialized.
func SetJSON(rawJSON []byte, ptr anyParamStruct) {
ptr.setMetadata(json.RawMessage(rawJSON))
}
// IsOmitted returns true if v is the zero value of its type.
//
// If IsOmitted is true, and the field uses a `json:"...,omitzero"` tag,
// the field will be omitted from the request.
//
// If v is set explicitly to the JSON value "null", IsOmitted returns false.
func IsOmitted(v any) bool {
if v == nil {
return false
}
if o, ok := v.(Optional); ok {
return o.isZero()
}
return reflect.ValueOf(v).IsZero()
}
// IsNull returns true if v was set to the JSON value null.
//
// To set a param to null use [NullStruct], [Null], [NullMap], or [NullSlice]
// depending on the type of v.
//
// IsNull returns false if the value is omitted.
func IsNull[T any](v T) bool {
if nullable, ok := any(v).(ParamNullable); ok {
return nullable.null()
}
switch reflect.TypeOf(v).Kind() {
case reflect.Slice, reflect.Map:
return sentinel.IsNull(v)
}
return false
}
// ParamNullable encapsulates all structs in parameters,
// and all [Opt] types in parameters.
type ParamNullable interface {
null() bool
}
// ParamStruct represents the set of all structs that are
// used in API parameters, by convention these usually end in
// "Params" or "Param".
type ParamStruct interface {
Overrides() (any, bool)
null() bool
extraFields() map[string]any
}
// A pointer to ParamStruct
type anyParamStruct interface {
setMetadata(any)
}
// This is an implementation detail and should never be explicitly set.
type InferPtr[T ParamStruct] interface {
setMetadata(any)
*T
}
// APIObject should be embedded in api object fields, preferably using an alias to make private
type APIObject struct{ metadata }
// APIUnion should be embedded in all api unions fields, preferably using an alias to make private
type APIUnion struct{ metadata }
// Overrides returns the value of the struct when it is created with
// [Override], the second argument helps differentiate an explicit null.
func (m metadata) Overrides() (any, bool) {
if _, ok := m.any.(metadataExtraFields); ok {
return nil, false
}
return m.any, m.any != nil
}
// ExtraFields returns the extra fields added to the JSON object.
func (m metadata) ExtraFields() map[string]any {
if extras, ok := m.any.(metadataExtraFields); ok {
return extras
}
return nil
}
// Omit can be used with [metadata.SetExtraFields] to ensure that a
// required field is omitted. This is useful as an escape hatch for
// when a required is unwanted for some unexpected reason.
const Omit forceOmit = -1
// SetExtraFields adds extra fields to the JSON object.
//
// SetExtraFields will override any existing fields with the same key.
// For security reasons, ensure this is only used with trusted input data.
//
// To intentionally omit a required field, use [Omit].
//
// foo.SetExtraFields(map[string]any{"bar": Omit})
//
// If the struct already contains the field ExtraFields, then this
// method will have no effect.
func (m *metadata) SetExtraFields(extraFields map[string]any) {
m.any = metadataExtraFields(extraFields)
}
// extraFields aliases [metadata.ExtraFields] to avoid name collisions.
func (m metadata) extraFields() map[string]any { return m.ExtraFields() }
func (m metadata) null() bool {
if _, ok := m.any.(metadataNull); ok {
return true
}
if msg, ok := m.any.(json.RawMessage); ok {
return string(msg) == "null"
}
return false
}
type metadata struct{ any }
type metadataNull struct{}
type metadataExtraFields map[string]any
func (m *metadata) setMetadata(override any) {
if override == nil {
m.any = metadataNull{}
return
}
m.any = override
}