fix: add involves edge from task to agent:nomos at creation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
2026-08-11 22:03:12 +02:00
parent 7d6a3320d4
commit febc153b7f
3384 changed files with 945212 additions and 2 deletions

View File

@@ -0,0 +1,58 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package apierror
import (
"fmt"
"net/http"
"net/http/httputil"
"github.com/openai/openai-go/internal/apijson"
"github.com/openai/openai-go/packages/respjson"
)
// Error represents an error that originates from the API, i.e. when a request is
// made and the API returns a response with a HTTP status code. Other errors are
// not wrapped by this SDK.
type Error struct {
Code string `json:"code,required"`
Message string `json:"message,required"`
Param string `json:"param,required"`
Type string `json:"type,required"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
Code respjson.Field
Message respjson.Field
Param respjson.Field
Type respjson.Field
ExtraFields map[string]respjson.Field
raw string
} `json:"-"`
StatusCode int
Request *http.Request
Response *http.Response
}
// Returns the unmodified JSON received from the API
func (r Error) RawJSON() string { return r.JSON.raw }
func (r *Error) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
func (r *Error) Error() string {
// Attempt to re-populate the response body
return fmt.Sprintf("%s %q: %d %s %s", r.Request.Method, r.Request.URL, r.Response.StatusCode, http.StatusText(r.Response.StatusCode), r.JSON.raw)
}
func (r *Error) DumpRequest(body bool) []byte {
if r.Request.GetBody != nil {
r.Request.Body, _ = r.Request.GetBody()
}
out, _ := httputil.DumpRequestOut(r.Request, body)
return out
}
func (r *Error) DumpResponse(body bool) []byte {
out, _ := httputil.DumpResponse(r.Response, body)
return out
}

View File

@@ -0,0 +1,465 @@
package apiform
import (
"fmt"
"io"
"mime/multipart"
"net/textproto"
"path"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/openai/openai-go/packages/param"
)
var encoders sync.Map // map[encoderEntry]encoderFunc
func Marshal(value any, writer *multipart.Writer) error {
e := &encoder{
dateFormat: time.RFC3339,
arrayFmt: "brackets",
}
return e.marshal(value, writer)
}
func MarshalRoot(value any, writer *multipart.Writer) error {
e := &encoder{
root: true,
dateFormat: time.RFC3339,
arrayFmt: "brackets",
}
return e.marshal(value, writer)
}
func MarshalWithSettings(value any, writer *multipart.Writer, arrayFormat string) error {
e := &encoder{
arrayFmt: arrayFormat,
dateFormat: time.RFC3339,
}
return e.marshal(value, writer)
}
type encoder struct {
arrayFmt string
dateFormat string
root bool
}
type encoderFunc func(key string, value reflect.Value, writer *multipart.Writer) error
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (e *encoder) marshal(value any, writer *multipart.Writer) error {
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil
}
typ := val.Type()
enc := e.typeEncoder(typ)
return enc("", val, writer)
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value, writer *multipart.Writer) error {
wg.Wait()
return f(key, v, writer)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder()
}
if t.Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) {
return e.newReaderTypeEncoder()
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.typeEncoder(inner)
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if !v.IsValid() || v.IsNil() {
return nil
}
return innerEncoder(key, v.Elem(), writer)
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Slice, reflect.Array:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
// Note that we could use `gjson` to encode these types but it would complicate our
// code more and this current code shouldn't cause any issues
case reflect.String:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, v.String())
}
case reflect.Bool:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if v.Bool() {
return writer.WriteField(key, "true")
}
return writer.WriteField(key, "false")
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatUint(v.Uint(), 10))
}
case reflect.Float32:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 32))
}
case reflect.Float64:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
default:
return func(key string, v reflect.Value, writer *multipart.Writer) error {
return fmt.Errorf("unknown type received at primitive encoder: %s", t.String())
}
}
}
func arrayKeyEncoder(arrayFmt string) func(string, int) string {
var keyFn func(string, int) string
switch arrayFmt {
case "comma", "repeat":
keyFn = func(k string, _ int) string { return k }
case "brackets":
keyFn = func(key string, _ int) string { return key + "[]" }
case "indices:dots":
keyFn = func(k string, i int) string {
if k == "" {
return strconv.Itoa(i)
}
return k + "." + strconv.Itoa(i)
}
case "indices:brackets":
keyFn = func(k string, i int) string {
if k == "" {
return strconv.Itoa(i)
}
return k + "[" + strconv.Itoa(i) + "]"
}
}
return keyFn
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
keyFn := arrayKeyEncoder(e.arrayFmt)
return func(key string, v reflect.Value, writer *multipart.Writer) error {
if keyFn == nil {
return fmt.Errorf("apiform: unsupported array format")
}
for i := 0; i < v.Len(); i++ {
err := itemEncoder(keyFn(key, i), v.Index(i), writer)
if err != nil {
return err
}
}
return nil
}
}
func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
return e.newRichFieldTypeEncoder(t)
}
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Type == paramUnionType && t.Field(i).Anonymous {
return e.newStructUnionTypeEncoder(t)
}
}
encoderFields := []encoderField{}
extraEncoder := (*encoderField)(nil)
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseFormStructTag(field)
if !ok {
continue
}
// We only want to support unexported field if they're tagged with
// `extras` because that field shouldn't be part of the public API. We
// also want to only keep the top level extras
if ptag.extras && len(index) == 0 {
extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx}
continue
}
if ptag.name == "-" || ptag.name == "" {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
var encoderFn encoderFunc
if ptag.omitzero {
typeEncoderFn := e.typeEncoder(field.Type)
encoderFn = func(key string, value reflect.Value, writer *multipart.Writer) error {
if value.IsZero() {
return nil
}
return typeEncoderFn(key, value, writer)
}
} else {
encoderFn = e.typeEncoder(field.Type)
}
encoderFields = append(encoderFields, encoderField{ptag, encoderFn, idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
// Ensure deterministic output by sorting by lexicographic order
sort.Slice(encoderFields, func(i, j int) bool {
return encoderFields[i].tag.name < encoderFields[j].tag.name
})
return func(key string, value reflect.Value, writer *multipart.Writer) error {
if key != "" {
key = key + "."
}
for _, ef := range encoderFields {
field := value.FieldByIndex(ef.idx)
err := ef.fn(key+ef.tag.name, field, writer)
if err != nil {
return err
}
}
if extraEncoder != nil {
err := e.encodeMapEntries(key, value.FieldByIndex(extraEncoder.idx), writer)
if err != nil {
return err
}
}
return nil
}
}
var paramUnionType = reflect.TypeOf((*param.APIUnion)(nil)).Elem()
func (e *encoder) newStructUnionTypeEncoder(t reflect.Type) encoderFunc {
var fieldEncoders []encoderFunc
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type == paramUnionType && field.Anonymous {
fieldEncoders = append(fieldEncoders, nil)
continue
}
fieldEncoders = append(fieldEncoders, e.typeEncoder(field.Type))
}
return func(key string, value reflect.Value, writer *multipart.Writer) error {
for i := 0; i < t.NumField(); i++ {
if value.Field(i).Type() == paramUnionType {
continue
}
if !value.Field(i).IsZero() {
return fieldEncoders[i](key, value.Field(i), writer)
}
}
return fmt.Errorf("apiform: union %s has no field set", t.String())
}
}
func (e *encoder) newTimeTypeEncoder() encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value, writer *multipart.Writer) error {
return writer.WriteField(key, value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format))
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
value = value.Elem()
if !value.IsValid() {
return nil
}
return e.typeEncoder(value.Type())(key, value, writer)
}
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
func (e *encoder) newReaderTypeEncoder() encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
reader, ok := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader)
if !ok {
return nil
}
filename := "anonymous_file"
contentType := "application/octet-stream"
if named, ok := reader.(interface{ Filename() string }); ok {
filename = named.Filename()
} else if named, ok := reader.(interface{ Name() string }); ok {
filename = path.Base(named.Name())
}
if typed, ok := reader.(interface{ ContentType() string }); ok {
contentType = typed.ContentType()
}
// Below is taken almost 1-for-1 from [multipart.CreateFormFile]
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(key), escapeQuotes(filename)))
h.Set("Content-Type", contentType)
filewriter, err := writer.CreatePart(h)
if err != nil {
return err
}
_, err = io.Copy(filewriter, reader)
return err
}
}
// Given a []byte of json (may either be an empty object or an object that already contains entries)
// encode all of the entries in the map to the json byte array.
func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipart.Writer) error {
type mapPair struct {
key string
value reflect.Value
}
if key != "" {
key = key + "."
}
pairs := []mapPair{}
iter := v.MapRange()
for iter.Next() {
if iter.Key().Type().Kind() == reflect.String {
pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()})
} else {
return fmt.Errorf("cannot encode a map with a non string key")
}
}
// Ensure deterministic output
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].key < pairs[j].key
})
elementEncoder := e.typeEncoder(v.Type().Elem())
for _, p := range pairs {
err := elementEncoder(key+string(p.key), p.value, writer)
if err != nil {
return err
}
}
return nil
}
func (e *encoder) newMapEncoder(_ reflect.Type) encoderFunc {
return func(key string, value reflect.Value, writer *multipart.Writer) error {
return e.encodeMapEntries(key, value, writer)
}
}
func WriteExtras(writer *multipart.Writer, extras map[string]any) (err error) {
for k, v := range extras {
str, ok := v.(string)
if !ok {
break
}
err = writer.WriteField(k, str)
if err != nil {
break
}
}
return
}

View File

@@ -0,0 +1,5 @@
package apiform
type Marshaler interface {
MarshalMultipart() ([]byte, string, error)
}

View File

@@ -0,0 +1,20 @@
package apiform
import (
"github.com/openai/openai-go/packages/param"
"mime/multipart"
"reflect"
)
func (e *encoder) newRichFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.newPrimitiveTypeEncoder(f.Type)
return func(key string, value reflect.Value, writer *multipart.Writer) error {
if opt, ok := value.Interface().(param.Optional); ok && opt.Valid() {
return enc(key, value.FieldByIndex(f.Index), writer)
} else if ok && param.IsNull(opt) {
return writer.WriteField(key, "null")
}
return nil
}
}

View File

@@ -0,0 +1,51 @@
package apiform
import (
"reflect"
"strings"
)
const jsonStructTag = "json"
const formStructTag = "form"
const formatStructTag = "format"
type parsedStructTag struct {
name string
required bool
extras bool
metadata bool
omitzero bool
}
func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(formStructTag)
if !ok {
raw, ok = field.Tag.Lookup(jsonStructTag)
}
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "required":
tag.required = true
case "extras":
tag.extras = true
case "metadata":
tag.metadata = true
case "omitzero":
tag.omitzero = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

View File

@@ -0,0 +1,691 @@
// The deserialization algorithm from apijson may be subject to improvements
// between minor versions, particularly with respect to calling [json.Unmarshal]
// into param unions.
package apijson
import (
"encoding/json"
"fmt"
"github.com/openai/openai-go/packages/param"
"reflect"
"strconv"
"sync"
"time"
"unsafe"
"github.com/tidwall/gjson"
)
// decoders is a synchronized map with roughly the following type:
// map[reflect.Type]decoderFunc
var decoders sync.Map
// Unmarshal is similar to [encoding/json.Unmarshal] and parses the JSON-encoded
// data and stores it in the given pointer.
func Unmarshal(raw []byte, to any) error {
d := &decoderBuilder{dateFormat: time.RFC3339}
return d.unmarshal(raw, to)
}
// UnmarshalRoot is like Unmarshal, but doesn't try to call MarshalJSON on the
// root element. Useful if a struct's UnmarshalJSON is overrode to use the
// behavior of this encoder versus the standard library.
func UnmarshalRoot(raw []byte, to any) error {
d := &decoderBuilder{dateFormat: time.RFC3339, root: true}
return d.unmarshal(raw, to)
}
// decoderBuilder contains the 'compile-time' state of the decoder.
type decoderBuilder struct {
// Whether or not this is the first element and called by [UnmarshalRoot], see
// the documentation there to see why this is necessary.
root bool
// The dateFormat (a format string for [time.Format]) which is chosen by the
// last struct tag that was seen.
dateFormat string
}
// decoderState contains the 'run-time' state of the decoder.
type decoderState struct {
strict bool
exactness exactness
validator *validationEntry
}
// Exactness refers to how close to the type the result was if deserialization
// was successful. This is useful in deserializing unions, where you want to try
// each entry, first with strict, then with looser validation, without actually
// having to do a lot of redundant work by marshalling twice (or maybe even more
// times).
type exactness int8
const (
// Some values had to fudged a bit, for example by converting a string to an
// int, or an enum with extra values.
loose exactness = iota
// There are some extra arguments, but other wise it matches the union.
extras
// Exactly right.
exact
)
type decoderFunc func(node gjson.Result, value reflect.Value, state *decoderState) error
type decoderField struct {
tag parsedStructTag
fn decoderFunc
idx []int
goname string
}
type decoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (d *decoderBuilder) unmarshal(raw []byte, to any) error {
value := reflect.ValueOf(to).Elem()
result := gjson.ParseBytes(raw)
if !value.IsValid() {
return fmt.Errorf("apijson: cannot marshal into invalid value")
}
return d.typeDecoder(value.Type())(result, value, &decoderState{strict: false, exactness: exact})
}
// unmarshalWithExactness is used for internal testing purposes.
func (d *decoderBuilder) unmarshalWithExactness(raw []byte, to any) (exactness, error) {
value := reflect.ValueOf(to).Elem()
result := gjson.ParseBytes(raw)
if !value.IsValid() {
return 0, fmt.Errorf("apijson: cannot marshal into invalid value")
}
state := decoderState{strict: false, exactness: exact}
err := d.typeDecoder(value.Type())(result, value, &state)
return state.exactness, err
}
func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc {
entry := decoderEntry{
Type: t,
dateFormat: d.dateFormat,
root: d.root,
}
if fi, ok := decoders.Load(entry); ok {
return fi.(decoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f decoderFunc
)
wg.Add(1)
fi, loaded := decoders.LoadOrStore(entry, decoderFunc(func(node gjson.Result, v reflect.Value, state *decoderState) error {
wg.Wait()
return f(node, v, state)
}))
if loaded {
return fi.(decoderFunc)
}
// Compute the real decoder and replace the indirect func with it.
f = d.newTypeDecoder(t)
wg.Done()
decoders.Store(entry, f)
return f
}
// validatedTypeDecoder wraps the type decoder with a validator. This is helpful
// for ensuring that enum fields are correct.
func (d *decoderBuilder) validatedTypeDecoder(t reflect.Type, entry *validationEntry) decoderFunc {
dec := d.typeDecoder(t)
if entry == nil {
return dec
}
// Thread the current validation entry through the decoder,
// but clean up in time for the next field.
return func(node gjson.Result, v reflect.Value, state *decoderState) error {
state.validator = entry
err := dec(node, v, state)
state.validator = nil
return err
}
}
func indirectUnmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error {
return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw))
}
func unmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error {
if v.Kind() == reflect.Pointer && v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
}
return v.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw))
}
func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return d.newTimeTypeDecoder(t)
}
if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
return d.newOptTypeDecoder(t)
}
if !d.root && t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) {
return unmarshalerDecoder
}
if !d.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) {
if _, ok := unionVariants[t]; !ok {
return indirectUnmarshalerDecoder
}
}
d.root = false
if _, ok := unionRegistry[t]; ok {
if isStructUnion(t) {
return d.newStructUnionDecoder(t)
}
return d.newUnionDecoder(t)
}
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerDecoder := d.typeDecoder(inner)
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
if !v.IsValid() {
return fmt.Errorf("apijson: unexpected invalid reflection value %+#v", v)
}
newValue := reflect.New(inner).Elem()
err := innerDecoder(n, newValue, state)
if err != nil {
return err
}
v.Set(newValue.Addr())
return nil
}
case reflect.Struct:
if isStructUnion(t) {
return d.newStructUnionDecoder(t)
}
return d.newStructTypeDecoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return d.newArrayTypeDecoder(t)
case reflect.Map:
return d.newMapDecoder(t)
case reflect.Interface:
return func(node gjson.Result, value reflect.Value, state *decoderState) error {
if !value.IsValid() {
return fmt.Errorf("apijson: unexpected invalid value %+#v", value)
}
if node.Value() != nil && value.CanSet() {
value.Set(reflect.ValueOf(node.Value()))
}
return nil
}
default:
return d.newPrimitiveTypeDecoder(t)
}
}
func (d *decoderBuilder) newMapDecoder(t reflect.Type) decoderFunc {
keyType := t.Key()
itemType := t.Elem()
itemDecoder := d.typeDecoder(itemType)
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
mapValue := reflect.MakeMapWithSize(t, len(node.Map()))
node.ForEach(func(key, value gjson.Result) bool {
// It's fine for us to just use `ValueOf` here because the key types will
// always be primitive types so we don't need to decode it using the standard pattern
keyValue := reflect.ValueOf(key.Value())
if !keyValue.IsValid() {
if err == nil {
err = fmt.Errorf("apijson: received invalid key type %v", keyValue.String())
}
return false
}
if keyValue.Type() != keyType {
if err == nil {
err = fmt.Errorf("apijson: expected key type %v but got %v", keyType, keyValue.Type())
}
return false
}
itemValue := reflect.New(itemType).Elem()
itemerr := itemDecoder(value, itemValue, state)
if itemerr != nil {
if err == nil {
err = itemerr
}
return false
}
mapValue.SetMapIndex(keyValue, itemValue)
return true
})
if err != nil {
return err
}
value.Set(mapValue)
return nil
}
}
func (d *decoderBuilder) newArrayTypeDecoder(t reflect.Type) decoderFunc {
itemDecoder := d.typeDecoder(t.Elem())
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
if !node.IsArray() {
return fmt.Errorf("apijson: could not deserialize to an array")
}
arrayNode := node.Array()
arrayValue := reflect.MakeSlice(reflect.SliceOf(t.Elem()), len(arrayNode), len(arrayNode))
for i, itemNode := range arrayNode {
err = itemDecoder(itemNode, arrayValue.Index(i), state)
if err != nil {
return err
}
}
value.Set(arrayValue)
return nil
}
}
func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc {
// map of json field name to struct field decoders
decoderFields := map[string]decoderField{}
anonymousDecoders := []decoderField{}
extraDecoder := (*decoderField)(nil)
var inlineDecoders []decoderField
validationEntries := validationRegistry[t]
for i := 0; i < t.NumField(); i++ {
idx := []int{i}
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
var validator *validationEntry
for _, entry := range validationEntries {
if entry.field.Offset == field.Offset {
validator = &entry
break
}
}
// If this is an embedded struct, traverse one level deeper to extract
// the fields and get their encoders as well.
if field.Anonymous {
anonymousDecoders = append(anonymousDecoders, decoderField{
fn: d.typeDecoder(field.Type),
idx: idx[:],
})
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
// We only want to support unexported fields if they're tagged with
// `extras` because that field shouldn't be part of the public API.
if ptag.extras {
extraDecoder = &decoderField{ptag, d.typeDecoder(field.Type.Elem()), idx, field.Name}
continue
}
if ptag.inline {
df := decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name}
inlineDecoders = append(inlineDecoders, df)
continue
}
if ptag.metadata {
continue
}
oldFormat := d.dateFormat
dateFormat, ok := parseFormatStructTag(field)
if ok {
switch dateFormat {
case "date-time":
d.dateFormat = time.RFC3339
case "date":
d.dateFormat = "2006-01-02"
}
}
decoderFields[ptag.name] = decoderField{
ptag,
d.validatedTypeDecoder(field.Type, validator),
idx, field.Name,
}
d.dateFormat = oldFormat
}
return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) {
if field := value.FieldByName("JSON"); field.IsValid() {
if raw := field.FieldByName("raw"); raw.IsValid() {
setUnexportedField(raw, node.Raw)
}
}
for _, decoder := range anonymousDecoders {
// ignore errors
decoder.fn(node, value.FieldByIndex(decoder.idx), state)
}
for _, inlineDecoder := range inlineDecoders {
var meta Field
dest := value.FieldByIndex(inlineDecoder.idx)
isValid := false
if dest.IsValid() && node.Type != gjson.Null {
inlineState := decoderState{exactness: state.exactness, strict: true}
err = inlineDecoder.fn(node, dest, &inlineState)
if err == nil {
isValid = true
}
}
if node.Type == gjson.Null {
meta = Field{
raw: node.Raw,
status: null,
}
} else if !isValid {
// If an inline decoder fails, unset the field and move on.
if dest.IsValid() {
dest.SetZero()
}
continue
} else if isValid {
meta = Field{
raw: node.Raw,
status: valid,
}
}
setMetadataSubField(value, inlineDecoder.idx, inlineDecoder.goname, meta)
}
typedExtraType := reflect.Type(nil)
typedExtraFields := reflect.Value{}
if extraDecoder != nil {
typedExtraType = value.FieldByIndex(extraDecoder.idx).Type()
typedExtraFields = reflect.MakeMap(typedExtraType)
}
untypedExtraFields := map[string]Field{}
for fieldName, itemNode := range node.Map() {
df, explicit := decoderFields[fieldName]
var (
dest reflect.Value
fn decoderFunc
meta Field
)
if explicit {
fn = df.fn
dest = value.FieldByIndex(df.idx)
}
if !explicit && extraDecoder != nil {
dest = reflect.New(typedExtraType.Elem()).Elem()
fn = extraDecoder.fn
}
isValid := false
if dest.IsValid() && itemNode.Type != gjson.Null {
err = fn(itemNode, dest, state)
if err == nil {
isValid = true
}
}
// Handle null [param.Opt]
if itemNode.Type == gjson.Null && dest.IsValid() && dest.Type().Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
dest.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(itemNode.Raw))
continue
}
if itemNode.Type == gjson.Null {
meta = Field{
raw: itemNode.Raw,
status: null,
}
} else if !isValid {
meta = Field{
raw: itemNode.Raw,
status: invalid,
}
} else if isValid {
meta = Field{
raw: itemNode.Raw,
status: valid,
}
}
if explicit {
setMetadataSubField(value, df.idx, df.goname, meta)
}
if !explicit {
untypedExtraFields[fieldName] = meta
}
if !explicit && extraDecoder != nil {
typedExtraFields.SetMapIndex(reflect.ValueOf(fieldName), dest)
}
}
if extraDecoder != nil && typedExtraFields.Len() > 0 {
value.FieldByIndex(extraDecoder.idx).Set(typedExtraFields)
}
// Set exactness to 'extras' if there are untyped, extra fields.
if len(untypedExtraFields) > 0 && state.exactness > extras {
state.exactness = extras
}
if len(untypedExtraFields) > 0 {
setMetadataExtraFields(value, []int{-1}, "ExtraFields", untypedExtraFields)
}
return nil
}
}
func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc {
switch t.Kind() {
case reflect.String:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetString(n.String())
if guardStrict(state, n.Type != gjson.String) {
return fmt.Errorf("apijson: failed to parse string strictly")
}
// Everything that is not an object can be loosely stringified.
if n.Type == gjson.JSON {
return fmt.Errorf("apijson: failed to parse string")
}
state.validateString(v)
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed string enum validation")
}
return nil
}
case reflect.Bool:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetBool(n.Bool())
if guardStrict(state, n.Type != gjson.True && n.Type != gjson.False) {
return fmt.Errorf("apijson: failed to parse bool strictly")
}
// Numbers and strings that are either 'true' or 'false' can be loosely
// deserialized as bool.
if n.Type == gjson.String && (n.Raw != "true" && n.Raw != "false") || n.Type == gjson.JSON {
return fmt.Errorf("apijson: failed to parse bool")
}
state.validateBool(v)
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed bool enum validation")
}
return nil
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetInt(n.Int())
if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num))) {
return fmt.Errorf("apijson: failed to parse int strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as numbers.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse int")
}
state.validateInt(v)
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed int enum validation")
}
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetUint(n.Uint())
if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num)) || n.Num < 0) {
return fmt.Errorf("apijson: failed to parse uint strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as uint.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse uint")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed uint enum validation")
}
return nil
}
case reflect.Float32, reflect.Float64:
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
v.SetFloat(n.Float())
if guardStrict(state, n.Type != gjson.Number) {
return fmt.Errorf("apijson: failed to parse float strictly")
}
// Numbers, booleans, and strings that maybe look like numbers can be
// loosely deserialized as floats.
if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) {
return fmt.Errorf("apijson: failed to parse float")
}
if guardUnknown(state, v) {
return fmt.Errorf("apijson: failed float enum validation")
}
return nil
}
default:
return func(node gjson.Result, v reflect.Value, state *decoderState) error {
return fmt.Errorf("unknown type received at primitive decoder: %s", t.String())
}
}
}
func (d *decoderBuilder) newOptTypeDecoder(t reflect.Type) decoderFunc {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
valueField, _ := t.FieldByName("Value")
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
state.validateOptKind(n, valueField.Type)
return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw))
}
}
func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc {
format := d.dateFormat
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
parsed, err := time.Parse(format, n.Str)
if err == nil {
v.Set(reflect.ValueOf(parsed).Convert(t))
return nil
}
if guardStrict(state, true) {
return err
}
layouts := []string{
"2006-01-02",
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04:05",
"2006-01-02 15:04:05Z07:00",
"2006-01-02 15:04:05Z0700",
"2006-01-02 15:04:05",
}
for _, layout := range layouts {
parsed, err := time.Parse(layout, n.Str)
if err == nil {
v.Set(reflect.ValueOf(parsed).Convert(t))
return nil
}
}
return fmt.Errorf("unable to leniently parse date-time string: %s", n.Str)
}
}
func setUnexportedField(field reflect.Value, value any) {
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(value))
}
func guardStrict(state *decoderState, cond bool) bool {
if !cond {
return false
}
if state.strict {
return true
}
state.exactness = loose
return false
}
func canParseAsNumber(str string) bool {
_, err := strconv.ParseFloat(str, 64)
return err == nil
}
var stringType = reflect.TypeOf(string(""))
func guardUnknown(state *decoderState, v reflect.Value) bool {
if have, ok := v.Interface().(interface{ IsKnown() bool }); guardStrict(state, ok && !have.IsKnown()) {
return true
}
constantString, ok := v.Interface().(interface{ Default() string })
named := v.Type() != stringType
if guardStrict(state, ok && named && v.Equal(reflect.ValueOf(constantString.Default()))) {
return true
}
return false
}

View File

@@ -0,0 +1,392 @@
package apijson
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/tidwall/sjson"
)
var encoders sync.Map // map[encoderEntry]encoderFunc
func Marshal(value any) ([]byte, error) {
e := &encoder{dateFormat: time.RFC3339}
return e.marshal(value)
}
func MarshalRoot(value any) ([]byte, error) {
e := &encoder{root: true, dateFormat: time.RFC3339}
return e.marshal(value)
}
type encoder struct {
dateFormat string
root bool
}
type encoderFunc func(value reflect.Value) ([]byte, error)
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
}
func (e *encoder) marshal(value any) ([]byte, error) {
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil, nil
}
typ := val.Type()
enc := e.typeEncoder(typ)
return enc(val)
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(v reflect.Value) ([]byte, error) {
wg.Wait()
return f(v)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func marshalerEncoder(v reflect.Value) ([]byte, error) {
return v.Interface().(json.Marshaler).MarshalJSON()
}
func indirectMarshalerEncoder(v reflect.Value) ([]byte, error) {
return v.Addr().Interface().(json.Marshaler).MarshalJSON()
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder()
}
if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return marshalerEncoder
}
if !e.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return indirectMarshalerEncoder
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.typeEncoder(inner)
return func(v reflect.Value) ([]byte, error) {
if !v.IsValid() || v.IsNil() {
return nil, nil
}
return innerEncoder(v.Elem())
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
// Note that we could use `gjson` to encode these types but it would complicate our
// code more and this current code shouldn't cause any issues
case reflect.String:
return func(v reflect.Value) ([]byte, error) {
return json.Marshal(v.Interface())
}
case reflect.Bool:
return func(v reflect.Value) ([]byte, error) {
if v.Bool() {
return []byte("true"), nil
}
return []byte("false"), nil
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatInt(v.Int(), 10)), nil
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatUint(v.Uint(), 10)), nil
}
case reflect.Float32:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 32)), nil
}
case reflect.Float64:
return func(v reflect.Value) ([]byte, error) {
return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 64)), nil
}
default:
return func(v reflect.Value) ([]byte, error) {
return nil, fmt.Errorf("unknown type received at primitive encoder: %s", t.String())
}
}
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
itemEncoder := e.typeEncoder(t.Elem())
return func(value reflect.Value) ([]byte, error) {
json := []byte("[]")
for i := 0; i < value.Len(); i++ {
var value, err = itemEncoder(value.Index(i))
if err != nil {
return nil, err
}
if value == nil {
// Assume that empty items should be inserted as `null` so that the output array
// will be the same length as the input array
value = []byte("null")
}
json, err = sjson.SetRawBytes(json, "-1", value)
if err != nil {
return nil, err
}
}
return json, nil
}
}
func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
encoderFields := []encoderField{}
extraEncoder := (*encoderField)(nil)
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If json tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
// We only want to support unexported field if they're tagged with
// `extras` because that field shouldn't be part of the public API. We
// also want to only keep the top level extras
if ptag.extras && len(index) == 0 {
extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx}
continue
}
if ptag.name == "-" {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
// Ensure deterministic output by sorting by lexicographic order
sort.Slice(encoderFields, func(i, j int) bool {
return encoderFields[i].tag.name < encoderFields[j].tag.name
})
return func(value reflect.Value) (json []byte, err error) {
json = []byte("{}")
for _, ef := range encoderFields {
field := value.FieldByIndex(ef.idx)
encoded, err := ef.fn(field)
if err != nil {
return nil, err
}
if encoded == nil {
continue
}
json, err = sjson.SetRawBytes(json, ef.tag.name, encoded)
if err != nil {
return nil, err
}
}
if extraEncoder != nil {
json, err = e.encodeMapEntries(json, value.FieldByIndex(extraEncoder.idx))
if err != nil {
return nil, err
}
}
return
}
}
func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.typeEncoder(f.Type)
return func(value reflect.Value) (json []byte, err error) {
present := value.FieldByName("Present")
if !present.Bool() {
return nil, nil
}
null := value.FieldByName("Null")
if null.Bool() {
return []byte("null"), nil
}
raw := value.FieldByName("Raw")
if !raw.IsNil() {
return e.typeEncoder(raw.Type())(raw)
}
return enc(value.FieldByName("Value"))
}
}
func (e *encoder) newTimeTypeEncoder() encoderFunc {
format := e.dateFormat
return func(value reflect.Value) (json []byte, err error) {
return []byte(`"` + value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format) + `"`), nil
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(value reflect.Value) ([]byte, error) {
value = value.Elem()
if !value.IsValid() {
return nil, nil
}
return e.typeEncoder(value.Type())(value)
}
}
// Given a []byte of json (may either be an empty object or an object that already contains entries)
// encode all of the entries in the map to the json byte array.
func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) {
type mapPair struct {
key []byte
value reflect.Value
}
pairs := []mapPair{}
keyEncoder := e.typeEncoder(v.Type().Key())
iter := v.MapRange()
for iter.Next() {
var encodedKeyString string
if iter.Key().Type().Kind() == reflect.String {
encodedKeyString = iter.Key().String()
} else {
var err error
encodedKeyBytes, err := keyEncoder(iter.Key())
if err != nil {
return nil, err
}
encodedKeyString = string(encodedKeyBytes)
}
encodedKey := []byte(sjsonReplacer.Replace(encodedKeyString))
pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()})
}
// Ensure deterministic output
sort.Slice(pairs, func(i, j int) bool {
return bytes.Compare(pairs[i].key, pairs[j].key) < 0
})
elementEncoder := e.typeEncoder(v.Type().Elem())
for _, p := range pairs {
encodedValue, err := elementEncoder(p.value)
if err != nil {
return nil, err
}
if len(encodedValue) == 0 {
continue
}
json, err = sjson.SetRawBytes(json, string(p.key), encodedValue)
if err != nil {
return nil, err
}
}
return json, nil
}
func (e *encoder) newMapEncoder(_ reflect.Type) encoderFunc {
return func(value reflect.Value) ([]byte, error) {
json := []byte("{}")
var err error
json, err = e.encodeMapEntries(json, value)
if err != nil {
return nil, err
}
return json, nil
}
}
// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have
// special characters that sjson interprets as a path.
var sjsonReplacer *strings.Replacer = strings.NewReplacer(".", "\\.", ":", "\\:", "*", "\\*")

View File

@@ -0,0 +1,145 @@
package apijson
import (
"fmt"
"reflect"
"slices"
"sync"
"github.com/tidwall/gjson"
)
/********************/
/* Validating Enums */
/********************/
type validationEntry struct {
field reflect.StructField
required bool
legalValues struct {
strings []string
// 1 represents true, 0 represents false, -1 represents either
bools int
ints []int64
}
}
type validatorFunc func(reflect.Value) exactness
var validators sync.Map
var validationRegistry = map[reflect.Type][]validationEntry{}
func RegisterFieldValidator[T any, V string | bool | int](fieldName string, values ...V) {
var t T
parentType := reflect.TypeOf(t)
if _, ok := validationRegistry[parentType]; !ok {
validationRegistry[parentType] = []validationEntry{}
}
// The following checks run at initialization time,
// it is impossible for them to panic if any tests pass.
if parentType.Kind() != reflect.Struct {
panic(fmt.Sprintf("apijson: cannot initialize validator for non-struct %s", parentType.String()))
}
var field reflect.StructField
found := false
for i := 0; i < parentType.NumField(); i++ {
ptag, ok := parseJSONStructTag(parentType.Field(i))
if ok && ptag.name == fieldName {
field = parentType.Field(i)
found = true
break
}
}
if !found {
panic(fmt.Sprintf("apijson: cannot find field %s in struct %s", fieldName, parentType.String()))
}
newEntry := validationEntry{field: field}
newEntry.legalValues.bools = -1 // default to either
switch values := any(values).(type) {
case []string:
newEntry.legalValues.strings = values
case []int:
newEntry.legalValues.ints = make([]int64, len(values))
for i, value := range values {
newEntry.legalValues.ints[i] = int64(value)
}
case []bool:
for i, value := range values {
var next int
if value {
next = 1
}
if i > 0 && newEntry.legalValues.bools != next {
newEntry.legalValues.bools = -1 // accept either
break
}
newEntry.legalValues.bools = next
}
}
// Store the information necessary to create a validator, so that we can use it
// lazily create the validator function when did.
validationRegistry[parentType] = append(validationRegistry[parentType], newEntry)
}
func (state *decoderState) validateString(v reflect.Value) {
if state.validator == nil {
return
}
if !slices.Contains(state.validator.legalValues.strings, v.String()) {
state.exactness = loose
}
}
func (state *decoderState) validateInt(v reflect.Value) {
if state.validator == nil {
return
}
if !slices.Contains(state.validator.legalValues.ints, v.Int()) {
state.exactness = loose
}
}
func (state *decoderState) validateBool(v reflect.Value) {
if state.validator == nil {
return
}
b := v.Bool()
if state.validator.legalValues.bools == 1 && b == false {
state.exactness = loose
} else if state.validator.legalValues.bools == 0 && b == true {
state.exactness = loose
}
}
func (state *decoderState) validateOptKind(node gjson.Result, t reflect.Type) {
switch node.Type {
case gjson.JSON:
state.exactness = loose
case gjson.Null:
return
case gjson.False, gjson.True:
if t.Kind() != reflect.Bool {
state.exactness = loose
}
case gjson.Number:
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return
default:
state.exactness = loose
}
case gjson.String:
if t.Kind() != reflect.String {
state.exactness = loose
}
}
}

View File

@@ -0,0 +1,23 @@
package apijson
type status uint8
const (
missing status = iota
null
invalid
valid
)
type Field struct {
raw string
status status
}
// Returns true if the field is explicitly `null` _or_ if it is not present at all (ie, missing).
// To check if the field's key is present in the JSON with an explicit null value,
// you must check `f.IsNull() && !f.IsMissing()`.
func (j Field) IsNull() bool { return j.status <= null }
func (j Field) IsMissing() bool { return j.status == missing }
func (j Field) IsInvalid() bool { return j.status == invalid }
func (j Field) Raw() string { return j.raw }

View File

@@ -0,0 +1,120 @@
package apijson
import (
"fmt"
"reflect"
)
// Port copies over values from one struct to another struct.
func Port(from any, to any) error {
toVal := reflect.ValueOf(to)
fromVal := reflect.ValueOf(from)
if toVal.Kind() != reflect.Ptr || toVal.IsNil() {
return fmt.Errorf("destination must be a non-nil pointer")
}
for toVal.Kind() == reflect.Ptr {
toVal = toVal.Elem()
}
toType := toVal.Type()
for fromVal.Kind() == reflect.Ptr {
fromVal = fromVal.Elem()
}
fromType := fromVal.Type()
if toType.Kind() != reflect.Struct {
return fmt.Errorf("destination must be a non-nil pointer to a struct (%v %v)", toType, toType.Kind())
}
values := map[string]reflect.Value{}
fields := map[string]reflect.Value{}
fromJSON := fromVal.FieldByName("JSON")
toJSON := toVal.FieldByName("JSON")
// Iterate through the fields of v and load all the "normal" fields in the struct to the map of
// string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j.
var getFields func(t reflect.Type, v reflect.Value)
getFields = func(t reflect.Type, v reflect.Value) {
j := v.FieldByName("JSON")
// Recurse into anonymous fields first, since the fields on the object should win over the fields in the
// embedded object.
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Anonymous {
getFields(field.Type, v.Field(i))
continue
}
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
ptag, ok := parseJSONStructTag(field)
if !ok || ptag.name == "-" || ptag.name == "" {
continue
}
values[ptag.name] = v.Field(i)
if j.IsValid() {
fields[ptag.name] = j.FieldByName(field.Name)
}
}
}
getFields(fromType, fromVal)
// Use the values from the previous step to populate the 'to' struct.
for i := 0; i < toType.NumField(); i++ {
field := toType.Field(i)
ptag, ok := parseJSONStructTag(field)
if !ok {
continue
}
if ptag.name == "-" {
continue
}
if value, ok := values[ptag.name]; ok {
delete(values, ptag.name)
if field.Type.Kind() == reflect.Interface {
toVal.Field(i).Set(value)
} else {
switch value.Kind() {
case reflect.String:
toVal.Field(i).SetString(value.String())
case reflect.Bool:
toVal.Field(i).SetBool(value.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
toVal.Field(i).SetInt(value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
toVal.Field(i).SetUint(value.Uint())
case reflect.Float32, reflect.Float64:
toVal.Field(i).SetFloat(value.Float())
default:
toVal.Field(i).Set(value)
}
}
}
if fromJSONField, ok := fields[ptag.name]; ok {
if toJSONField := toJSON.FieldByName(field.Name); toJSONField.IsValid() {
toJSONField.Set(fromJSONField)
}
}
}
// Finally, copy over the .JSON.raw and .JSON.ExtraFields
if toJSON.IsValid() {
if raw := toJSON.FieldByName("raw"); raw.IsValid() {
setUnexportedField(raw, fromJSON.Interface().(interface{ RawJSON() string }).RawJSON())
}
if toExtraFields := toJSON.FieldByName("ExtraFields"); toExtraFields.IsValid() {
if fromExtraFields := fromJSON.FieldByName("ExtraFields"); fromExtraFields.IsValid() {
setUnexportedField(toExtraFields, fromExtraFields.Interface())
}
}
}
return nil
}

View File

@@ -0,0 +1,51 @@
package apijson
import (
"reflect"
"github.com/tidwall/gjson"
)
type UnionVariant struct {
TypeFilter gjson.Type
DiscriminatorValue any
Type reflect.Type
}
var unionRegistry = map[reflect.Type]unionEntry{}
var unionVariants = map[reflect.Type]any{}
type unionEntry struct {
discriminatorKey string
variants []UnionVariant
}
func Discriminator[T any](value any) UnionVariant {
var zero T
return UnionVariant{
TypeFilter: gjson.JSON,
DiscriminatorValue: value,
Type: reflect.TypeOf(zero),
}
}
func RegisterUnion[T any](discriminator string, variants ...UnionVariant) {
typ := reflect.TypeOf((*T)(nil)).Elem()
unionRegistry[typ] = unionEntry{
discriminatorKey: discriminator,
variants: variants,
}
for _, variant := range variants {
unionVariants[variant.Type] = typ
}
}
// Useful to wrap a union type to force it to use [apijson.UnmarshalJSON] since you cannot define an
// UnmarshalJSON function on the interface itself.
type UnionUnmarshaler[T any] struct {
Value T
}
func (c *UnionUnmarshaler[T]) UnmarshalJSON(buf []byte) error {
return UnmarshalRoot(buf, &c.Value)
}

View File

@@ -0,0 +1,67 @@
package apijson
import (
"github.com/openai/openai-go/packages/respjson"
"reflect"
)
func getSubField(root reflect.Value, index []int, name string) reflect.Value {
strct := root.FieldByIndex(index[:len(index)-1])
if !strct.IsValid() {
panic("couldn't find encapsulating struct for field " + name)
}
meta := strct.FieldByName("JSON")
if !meta.IsValid() {
return reflect.Value{}
}
field := meta.FieldByName(name)
if !field.IsValid() {
return reflect.Value{}
}
return field
}
func setMetadataSubField(root reflect.Value, index []int, name string, meta Field) {
target := getSubField(root, index, name)
if !target.IsValid() {
return
}
if target.Type() == reflect.TypeOf(meta) {
target.Set(reflect.ValueOf(meta))
} else if respMeta := meta.toRespField(); target.Type() == reflect.TypeOf(respMeta) {
target.Set(reflect.ValueOf(respMeta))
}
}
func setMetadataExtraFields(root reflect.Value, index []int, name string, metaExtras map[string]Field) {
target := getSubField(root, index, name)
if !target.IsValid() {
return
}
if target.Type() == reflect.TypeOf(metaExtras) {
target.Set(reflect.ValueOf(metaExtras))
return
}
newMap := make(map[string]respjson.Field, len(metaExtras))
if target.Type() == reflect.TypeOf(newMap) {
for k, v := range metaExtras {
newMap[k] = v.toRespField()
}
target.Set(reflect.ValueOf(newMap))
}
}
func (f Field) toRespField() respjson.Field {
if f.IsMissing() {
return respjson.Field{}
} else if f.IsNull() {
return respjson.NewField("null")
} else if f.IsInvalid() {
return respjson.NewInvalidField(f.raw)
} else {
return respjson.NewField(f.raw)
}
}

View File

@@ -0,0 +1,47 @@
package apijson
import (
"reflect"
"strings"
)
const jsonStructTag = "json"
const formatStructTag = "format"
type parsedStructTag struct {
name string
required bool
extras bool
metadata bool
inline bool
}
func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(jsonStructTag)
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "required":
tag.required = true
case "extras":
tag.extras = true
case "metadata":
tag.metadata = true
case "inline":
tag.inline = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

View File

@@ -0,0 +1,202 @@
package apijson
import (
"errors"
"github.com/openai/openai-go/packages/param"
"reflect"
"github.com/tidwall/gjson"
)
var apiUnionType = reflect.TypeOf(param.APIUnion{})
func isStructUnion(t reflect.Type) bool {
if t.Kind() != reflect.Struct {
return false
}
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Type == apiUnionType && t.Field(i).Anonymous {
return true
}
}
return false
}
func RegisterDiscriminatedUnion[T any](key string, mappings map[string]reflect.Type) {
var t T
entry := unionEntry{
discriminatorKey: key,
variants: []UnionVariant{},
}
for k, typ := range mappings {
entry.variants = append(entry.variants, UnionVariant{
DiscriminatorValue: k,
Type: typ,
})
}
unionRegistry[reflect.TypeOf(t)] = entry
}
func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc {
type variantDecoder struct {
decoder decoderFunc
field reflect.StructField
discriminatorValue any
}
variants := []variantDecoder{}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Anonymous && field.Type == apiUnionType {
continue
}
decoder := d.typeDecoder(field.Type)
variants = append(variants, variantDecoder{
decoder: decoder,
field: field,
})
}
unionEntry, discriminated := unionRegistry[t]
for _, unionVariant := range unionEntry.variants {
for i := 0; i < len(variants); i++ {
variant := &variants[i]
if variant.field.Type.Elem() == unionVariant.Type {
variant.discriminatorValue = unionVariant.DiscriminatorValue
break
}
}
}
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
if discriminated && n.Type == gjson.JSON && len(unionEntry.discriminatorKey) != 0 {
discriminator := n.Get(unionEntry.discriminatorKey).Value()
for _, variant := range variants {
if discriminator == variant.discriminatorValue {
inner := v.FieldByIndex(variant.field.Index)
return variant.decoder(n, inner, state)
}
}
return errors.New("apijson: was not able to find discriminated union variant")
}
// Set bestExactness to worse than loose
bestExactness := loose - 1
bestVariant := -1
for i, variant := range variants {
// Pointers are used to discern JSON object variants from value variants
if n.Type != gjson.JSON && variant.field.Type.Kind() == reflect.Ptr {
continue
}
sub := decoderState{strict: state.strict, exactness: exact}
inner := v.FieldByIndex(variant.field.Index)
err := variant.decoder(n, inner, &sub)
if err != nil {
continue
}
if sub.exactness == exact {
bestExactness = exact
bestVariant = i
break
}
if sub.exactness > bestExactness {
bestExactness = sub.exactness
bestVariant = i
}
}
if bestExactness < loose {
return errors.New("apijson: was not able to coerce type as union")
}
if guardStrict(state, bestExactness != exact) {
return errors.New("apijson: was not able to coerce type as union strictly")
}
for i := 0; i < len(variants); i++ {
if i == bestVariant {
continue
}
v.FieldByIndex(variants[i].field.Index).SetZero()
}
return nil
}
}
// newUnionDecoder returns a decoderFunc that deserializes into a union using an
// algorithm roughly similar to Pydantic's [smart algorithm].
//
// Conceptually this is equivalent to choosing the best schema based on how 'exact'
// the deserialization is for each of the schemas.
//
// If there is a tie in the level of exactness, then the tie is broken
// left-to-right.
//
// [smart algorithm]: https://docs.pydantic.dev/latest/concepts/unions/#smart-mode
func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc {
unionEntry, ok := unionRegistry[t]
if !ok {
panic("apijson: couldn't find union of type " + t.String() + " in union registry")
}
decoders := []decoderFunc{}
for _, variant := range unionEntry.variants {
decoder := d.typeDecoder(variant.Type)
decoders = append(decoders, decoder)
}
return func(n gjson.Result, v reflect.Value, state *decoderState) error {
// If there is a discriminator match, circumvent the exactness logic entirely
for idx, variant := range unionEntry.variants {
decoder := decoders[idx]
if variant.TypeFilter != n.Type {
continue
}
if len(unionEntry.discriminatorKey) != 0 {
discriminatorValue := n.Get(unionEntry.discriminatorKey).Value()
if discriminatorValue == variant.DiscriminatorValue {
inner := reflect.New(variant.Type).Elem()
err := decoder(n, inner, state)
v.Set(inner)
return err
}
}
}
// Set bestExactness to worse than loose
bestExactness := loose - 1
for idx, variant := range unionEntry.variants {
decoder := decoders[idx]
if variant.TypeFilter != n.Type {
continue
}
sub := decoderState{strict: state.strict, exactness: exact}
inner := reflect.New(variant.Type).Elem()
err := decoder(n, inner, &sub)
if err != nil {
continue
}
if sub.exactness == exact {
v.Set(inner)
return nil
}
if sub.exactness > bestExactness {
v.Set(inner)
bestExactness = sub.exactness
}
}
if bestExactness < loose {
return errors.New("apijson: was not able to coerce type as union")
}
if guardStrict(state, bestExactness != exact) {
return errors.New("apijson: was not able to coerce type as union strictly")
}
return nil
}
}

View File

@@ -0,0 +1,415 @@
package apiquery
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/openai/openai-go/packages/param"
)
var encoders sync.Map // map[reflect.Type]encoderFunc
type encoder struct {
dateFormat string
root bool
settings QuerySettings
}
type encoderFunc func(key string, value reflect.Value) ([]Pair, error)
type encoderField struct {
tag parsedStructTag
fn encoderFunc
idx []int
}
type encoderEntry struct {
reflect.Type
dateFormat string
root bool
settings QuerySettings
}
type Pair struct {
key string
value string
}
func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
dateFormat: e.dateFormat,
root: e.root,
settings: e.settings,
}
if fi, ok := encoders.Load(entry); ok {
return fi.(encoderFunc)
}
// To deal with recursive types, populate the map with an
// indirect func before we build it. This type waits on the
// real func (f) to be ready and then calls it. This indirect
// func is only used for recursive types.
var (
wg sync.WaitGroup
f encoderFunc
)
wg.Add(1)
fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value) ([]Pair, error) {
wg.Wait()
return f(key, v)
}))
if loaded {
return fi.(encoderFunc)
}
// Compute the real encoder and replace the indirect func with it.
f = e.newTypeEncoder(t)
wg.Done()
encoders.Store(entry, f)
return f
}
func marshalerEncoder(key string, value reflect.Value) ([]Pair, error) {
s, err := value.Interface().(json.Marshaler).MarshalJSON()
if err != nil {
return nil, fmt.Errorf("apiquery: json fallback marshal error %s", err)
}
return []Pair{{key, string(s)}}, nil
}
func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return e.newTimeTypeEncoder(t)
}
if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
return e.newRichFieldTypeEncoder(t)
}
if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return marshalerEncoder
}
e.root = false
switch t.Kind() {
case reflect.Pointer:
encoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair, err error) {
if !value.IsValid() || value.IsNil() {
return
}
return encoder(key, value.Elem())
}
case reflect.Struct:
return e.newStructTypeEncoder(t)
case reflect.Array:
fallthrough
case reflect.Slice:
return e.newArrayTypeEncoder(t)
case reflect.Map:
return e.newMapEncoder(t)
case reflect.Interface:
return e.newInterfaceEncoder()
default:
return e.newPrimitiveTypeEncoder(t)
}
}
func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
if t.Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
return e.newRichFieldTypeEncoder(t)
}
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Type == paramUnionType && t.Field(i).Anonymous {
return e.newStructUnionTypeEncoder(t)
}
}
encoderFields := []encoderField{}
// This helper allows us to recursively collect field encoders into a flat
// array. The parameter `index` keeps track of the access patterns necessary
// to get to some field.
var collectEncoderFields func(r reflect.Type, index []int)
collectEncoderFields = func(r reflect.Type, index []int) {
for i := 0; i < r.NumField(); i++ {
idx := append(index, i)
field := t.FieldByIndex(idx)
if !field.IsExported() {
continue
}
// If this is an embedded struct, traverse one level deeper to extract
// the field and get their encoders as well.
if field.Anonymous {
collectEncoderFields(field.Type, idx)
continue
}
// If query tag is not present, then we skip, which is intentionally
// different behavior from the stdlib.
ptag, ok := parseQueryStructTag(field)
if !ok {
continue
}
if (ptag.name == "-" || ptag.name == "") && !ptag.inline {
continue
}
dateFormat, ok := parseFormatStructTag(field)
oldFormat := e.dateFormat
if ok {
switch dateFormat {
case "date-time":
e.dateFormat = time.RFC3339
case "date":
e.dateFormat = "2006-01-02"
}
}
var encoderFn encoderFunc
if ptag.omitzero {
typeEncoderFn := e.typeEncoder(field.Type)
encoderFn = func(key string, value reflect.Value) ([]Pair, error) {
if value.IsZero() {
return nil, nil
}
return typeEncoderFn(key, value)
}
} else {
encoderFn = e.typeEncoder(field.Type)
}
encoderFields = append(encoderFields, encoderField{ptag, encoderFn, idx})
e.dateFormat = oldFormat
}
}
collectEncoderFields(t, []int{})
return func(key string, value reflect.Value) (pairs []Pair, err error) {
for _, ef := range encoderFields {
var subkey string = e.renderKeyPath(key, ef.tag.name)
if ef.tag.inline {
subkey = key
}
field := value.FieldByIndex(ef.idx)
subpairs, suberr := ef.fn(subkey, field)
if suberr != nil {
err = suberr
}
pairs = append(pairs, subpairs...)
}
return
}
}
var paramUnionType = reflect.TypeOf((*param.APIUnion)(nil)).Elem()
func (e *encoder) newStructUnionTypeEncoder(t reflect.Type) encoderFunc {
var fieldEncoders []encoderFunc
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type == paramUnionType && field.Anonymous {
fieldEncoders = append(fieldEncoders, nil)
continue
}
fieldEncoders = append(fieldEncoders, e.typeEncoder(field.Type))
}
return func(key string, value reflect.Value) (pairs []Pair, err error) {
for i := 0; i < t.NumField(); i++ {
if value.Field(i).Type() == paramUnionType {
continue
}
if !value.Field(i).IsZero() {
return fieldEncoders[i](key, value.Field(i))
}
}
return nil, fmt.Errorf("apiquery: union %s has no field set", t.String())
}
}
func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc {
keyEncoder := e.typeEncoder(t.Key())
elementEncoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair, err error) {
iter := value.MapRange()
for iter.Next() {
encodedKey, err := keyEncoder("", iter.Key())
if err != nil {
return nil, err
}
if len(encodedKey) != 1 {
return nil, fmt.Errorf("apiquery: unexpected number of parts for encoded map key, map may contain non-primitive")
}
subkey := encodedKey[0].value
keyPath := e.renderKeyPath(key, subkey)
subpairs, suberr := elementEncoder(keyPath, iter.Value())
if suberr != nil {
err = suberr
}
pairs = append(pairs, subpairs...)
}
return
}
}
func (e *encoder) renderKeyPath(key string, subkey string) string {
if len(key) == 0 {
return subkey
}
if e.settings.NestedFormat == NestedQueryFormatDots {
return fmt.Sprintf("%s.%s", key, subkey)
}
return fmt.Sprintf("%s[%s]", key, subkey)
}
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
switch e.settings.ArrayFormat {
case ArrayQueryFormatComma:
innerEncoder := e.typeEncoder(t.Elem())
return func(key string, v reflect.Value) ([]Pair, error) {
elements := []string{}
for i := 0; i < v.Len(); i++ {
innerPairs, err := innerEncoder("", v.Index(i))
if err != nil {
return nil, err
}
for _, pair := range innerPairs {
elements = append(elements, pair.value)
}
}
if len(elements) == 0 {
return []Pair{}, nil
}
return []Pair{{key, strings.Join(elements, ",")}}, nil
}
case ArrayQueryFormatRepeat:
innerEncoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair, err error) {
for i := 0; i < value.Len(); i++ {
subpairs, suberr := innerEncoder(key, value.Index(i))
if suberr != nil {
err = suberr
}
pairs = append(pairs, subpairs...)
}
return
}
case ArrayQueryFormatIndices:
panic("The array indices format is not supported yet")
case ArrayQueryFormatBrackets:
innerEncoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair, err error) {
pairs = []Pair{}
for i := 0; i < value.Len(); i++ {
subpairs, suberr := innerEncoder(key+"[]", value.Index(i))
if suberr != nil {
err = suberr
}
pairs = append(pairs, subpairs...)
}
return
}
default:
panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat))
}
}
func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
switch t.Kind() {
case reflect.Pointer:
inner := t.Elem()
innerEncoder := e.newPrimitiveTypeEncoder(inner)
return func(key string, v reflect.Value) ([]Pair, error) {
if !v.IsValid() || v.IsNil() {
return nil, nil
}
return innerEncoder(key, v.Elem())
}
case reflect.String:
return func(key string, v reflect.Value) ([]Pair, error) {
return []Pair{{key, v.String()}}, nil
}
case reflect.Bool:
return func(key string, v reflect.Value) ([]Pair, error) {
if v.Bool() {
return []Pair{{key, "true"}}, nil
}
return []Pair{{key, "false"}}, nil
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
return func(key string, v reflect.Value) ([]Pair, error) {
return []Pair{{key, strconv.FormatInt(v.Int(), 10)}}, nil
}
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return func(key string, v reflect.Value) ([]Pair, error) {
return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}}, nil
}
case reflect.Float32, reflect.Float64:
return func(key string, v reflect.Value) ([]Pair, error) {
return []Pair{{key, strconv.FormatFloat(v.Float(), 'f', -1, 64)}}, nil
}
case reflect.Complex64, reflect.Complex128:
bitSize := 64
if t.Kind() == reflect.Complex128 {
bitSize = 128
}
return func(key string, v reflect.Value) ([]Pair, error) {
return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}}, nil
}
default:
return func(key string, v reflect.Value) ([]Pair, error) {
return nil, nil
}
}
}
func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.typeEncoder(f.Type)
return func(key string, value reflect.Value) ([]Pair, error) {
present := value.FieldByName("Present")
if !present.Bool() {
return nil, nil
}
null := value.FieldByName("Null")
if null.Bool() {
return nil, fmt.Errorf("apiquery: field cannot be null")
}
raw := value.FieldByName("Raw")
if !raw.IsNil() {
return e.typeEncoder(raw.Type())(key, raw)
}
return enc(key, value.FieldByName("Value"))
}
}
func (e *encoder) newTimeTypeEncoder(_ reflect.Type) encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value) ([]Pair, error) {
return []Pair{{
key,
value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format),
}}, nil
}
}
func (e encoder) newInterfaceEncoder() encoderFunc {
return func(key string, value reflect.Value) ([]Pair, error) {
value = value.Elem()
if !value.IsValid() {
return nil, nil
}
return e.typeEncoder(value.Type())(key, value)
}
}

View File

@@ -0,0 +1,55 @@
package apiquery
import (
"net/url"
"reflect"
"time"
)
func MarshalWithSettings(value any, settings QuerySettings) (url.Values, error) {
e := encoder{time.RFC3339, true, settings}
kv := url.Values{}
val := reflect.ValueOf(value)
if !val.IsValid() {
return nil, nil
}
typ := val.Type()
pairs, err := e.typeEncoder(typ)("", val)
if err != nil {
return nil, err
}
for _, pair := range pairs {
kv.Add(pair.key, pair.value)
}
return kv, nil
}
func Marshal(value any) (url.Values, error) {
return MarshalWithSettings(value, QuerySettings{})
}
type Queryer interface {
URLQuery() (url.Values, error)
}
type QuerySettings struct {
NestedFormat NestedQueryFormat
ArrayFormat ArrayQueryFormat
}
type NestedQueryFormat int
const (
NestedQueryFormatBrackets NestedQueryFormat = iota
NestedQueryFormatDots
)
type ArrayQueryFormat int
const (
ArrayQueryFormatComma ArrayQueryFormat = iota
ArrayQueryFormatRepeat
ArrayQueryFormatIndices
ArrayQueryFormatBrackets
)

View File

@@ -0,0 +1,20 @@
package apiquery
import (
"reflect"
"github.com/openai/openai-go/packages/param"
)
func (e *encoder) newRichFieldTypeEncoder(t reflect.Type) encoderFunc {
f, _ := t.FieldByName("Value")
enc := e.typeEncoder(f.Type)
return func(key string, value reflect.Value) ([]Pair, error) {
if opt, ok := value.Interface().(param.Optional); ok && opt.Valid() {
return enc(key, value.FieldByIndex(f.Index))
} else if ok && param.IsNull(opt) {
return []Pair{{key, "null"}}, nil
}
return nil, nil
}
}

View File

@@ -0,0 +1,44 @@
package apiquery
import (
"reflect"
"strings"
)
const queryStructTag = "query"
const formatStructTag = "format"
type parsedStructTag struct {
name string
omitempty bool
omitzero bool
inline bool
}
func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(queryStructTag)
if !ok {
return
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
return tag, false
}
tag.name = parts[0]
for _, part := range parts[1:] {
switch part {
case "omitzero":
tag.omitzero = true
case "omitempty":
tag.omitempty = true
case "inline":
tag.inline = true
}
}
return
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
return
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import (
"unicode"
"unicode/utf8"
)
// foldName returns a folded string such that foldName(x) == foldName(y)
// is identical to bytes.EqualFold(x, y).
func foldName(in []byte) []byte {
// This is inlinable to take advantage of "function outlining".
var arr [32]byte // large enough for most JSON names
return appendFoldedName(arr[:0], in)
}
func appendFoldedName(out, in []byte) []byte {
for i := 0; i < len(in); {
// Handle single-byte ASCII.
if c := in[i]; c < utf8.RuneSelf {
if 'a' <= c && c <= 'z' {
c -= 'a' - 'A'
}
out = append(out, c)
i++
continue
}
// Handle multi-byte Unicode.
r, n := utf8.DecodeRune(in[i:])
out = utf8.AppendRune(out, foldRune(r))
i += n
}
return out
}
// foldRune is returns the smallest rune for all runes in the same fold set.
func foldRune(r rune) rune {
for {
r2 := unicode.SimpleFold(r)
if r2 <= r {
return r2
}
r = r2
}
}

View File

@@ -0,0 +1,182 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import "bytes"
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must be used.
func HTMLEscape(dst *bytes.Buffer, src []byte) {
dst.Grow(len(src))
dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src))
}
func appendHTMLEscape(dst, src []byte) []byte {
// The characters can only appear in string literals,
// so just scan the string one byte at a time.
start := 0
for i, c := range src {
if c == '<' || c == '>' || c == '&' {
dst = append(dst, src[start:i]...)
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
start = i + 1
}
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
dst = append(dst, src[start:i]...)
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
start = i + len("\u2029")
}
}
return append(dst, src[start:]...)
}
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
dst.Grow(len(src))
b := dst.AvailableBuffer()
b, err := appendCompact(b, src, false)
dst.Write(b)
return err
}
func appendCompact(dst, src []byte, escape bool) ([]byte, error) {
origLen := len(dst)
scan := newScanner()
defer freeScanner(scan)
start := 0
for i, c := range src {
if escape && (c == '<' || c == '>' || c == '&') {
if start < i {
dst = append(dst, src[start:i]...)
}
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
start = i + 1
}
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
if start < i {
dst = append(dst, src[start:i]...)
}
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
start = i + 3
}
v := scan.step(scan, c)
if v >= scanSkipSpace {
if v == scanError {
break
}
if start < i {
dst = append(dst, src[start:i]...)
}
start = i + 1
}
}
if scan.eof() == scanError {
return dst[:origLen], scan.err
}
if start < len(src) {
dst = append(dst, src[start:]...)
}
return dst, nil
}
func appendNewline(dst []byte, prefix, indent string, depth int) []byte {
dst = append(dst, '\n')
dst = append(dst, prefix...)
for i := 0; i < depth; i++ {
dst = append(dst, indent...)
}
return dst
}
// indentGrowthFactor specifies the growth factor of indenting JSON input.
// Empirically, the growth factor was measured to be between 1.4x to 1.8x
// for some set of compacted JSON with the indent being a single tab.
// Specify a growth factor slightly larger than what is observed
// to reduce probability of allocation in appendIndent.
// A factor no higher than 2 ensures that wasted space never exceeds 50%.
const indentGrowthFactor = 2
// Indent appends to dst an indented form of the JSON-encoded src.
// Each element in a JSON object or array begins on a new,
// indented line beginning with prefix followed by one or more
// copies of indent according to the indentation nesting.
// The data appended to dst does not begin with the prefix nor
// any indentation, to make it easier to embed inside other formatted JSON data.
// Although leading space characters (space, tab, carriage return, newline)
// at the beginning of src are dropped, trailing space characters
// at the end of src are preserved and copied to dst.
// For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
dst.Grow(indentGrowthFactor * len(src))
b := dst.AvailableBuffer()
b, err := appendIndent(b, src, prefix, indent)
dst.Write(b)
return err
}
func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
origLen := len(dst)
scan := newScanner()
defer freeScanner(scan)
needIndent := false
depth := 0
for _, c := range src {
scan.bytes++
v := scan.step(scan, c)
if v == scanSkipSpace {
continue
}
if v == scanError {
break
}
if needIndent && v != scanEndObject && v != scanEndArray {
needIndent = false
depth++
dst = appendNewline(dst, prefix, indent, depth)
}
// Emit semantically uninteresting bytes
// (in particular, punctuation in strings) unmodified.
if v == scanContinue {
dst = append(dst, c)
continue
}
// Add spacing around real punctuation.
switch c {
case '{', '[':
// delay indent so that empty object and array are formatted as {} and [].
needIndent = true
dst = append(dst, c)
case ',':
dst = append(dst, c)
dst = appendNewline(dst, prefix, indent, depth)
case ':':
dst = append(dst, c, ' ')
case '}', ']':
if needIndent {
// suppress indent in empty object/array
needIndent = false
} else {
depth--
dst = appendNewline(dst, prefix, indent, depth)
}
dst = append(dst, c)
default:
dst = append(dst, c)
}
}
if scan.eof() == scanError {
return dst[:origLen], scan.err
}
return dst, nil
}

View File

@@ -0,0 +1,610 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
// JSON value parser state machine.
// Just about at the limit of what is reasonable to write by hand.
// Some parts are a bit tedious, but overall it nicely factors out the
// otherwise common code from the multiple scanning functions
// in this package (Compact, Indent, checkValid, etc).
//
// This file starts with two simple examples using the scanner
// before diving into the scanner itself.
import (
"strconv"
"sync"
)
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
scan := newScanner()
defer freeScanner(scan)
return checkValid(data, scan) == nil
}
// checkValid verifies that data is valid JSON-encoded data.
// scan is passed in for use by checkValid to avoid an allocation.
// checkValid returns nil or a SyntaxError.
func checkValid(data []byte, scan *scanner) error {
scan.reset()
for _, c := range data {
scan.bytes++
if scan.step(scan, c) == scanError {
return scan.err
}
}
if scan.eof() == scanError {
return scan.err
}
return nil
}
// A SyntaxError is a description of a JSON syntax error.
// [Unmarshal] will return a SyntaxError if the JSON can't be parsed.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
// A scanner is a JSON scanning state machine.
// Callers call scan.reset and then pass bytes in one at a time
// by calling scan.step(&scan, c) for each byte.
// The return value, referred to as an opcode, tells the
// caller about significant parsing events like beginning
// and ending literals, objects, and arrays, so that the
// caller can follow along if it wishes.
// The return value scanEnd indicates that a single top-level
// JSON value has been completed, *before* the byte that
// just got passed in. (The indication must be delayed in order
// to recognize the end of numbers: is 123 a whole value or
// the beginning of 12345e+6?).
type scanner struct {
// The step is a func to be called to execute the next transition.
// Also tried using an integer constant and a single func
// with a switch, but using the func directly was 10% faster
// on a 64-bit Mac Mini, and it's nicer to read.
step func(*scanner, byte) int
// Reached end of top-level value.
endTop bool
// Stack of what we're in the middle of - array values, object keys, object values.
parseState []int
// Error that happened, if any.
err error
// total bytes consumed, updated by decoder.Decode (and deliberately
// not set to zero by scan.reset)
bytes int64
}
var scannerPool = sync.Pool{
New: func() any {
return &scanner{}
},
}
func newScanner() *scanner {
scan := scannerPool.Get().(*scanner)
// scan.reset by design doesn't set bytes to zero
scan.bytes = 0
scan.reset()
return scan
}
func freeScanner(scan *scanner) {
// Avoid hanging on to too much memory in extreme cases.
if len(scan.parseState) > 1024 {
scan.parseState = nil
}
scannerPool.Put(scan)
}
// These values are returned by the state transition functions
// assigned to scanner.state and the method scanner.eof.
// They give details about the current state of the scan that
// callers might be interested to know about.
// It is okay to ignore the return value of any particular
// call to scanner.state: if one call returns scanError,
// every subsequent call will return scanError too.
const (
// Continue.
scanContinue = iota // uninteresting byte
scanBeginLiteral // end implied by next result != scanContinue
scanBeginObject // begin object
scanObjectKey // just finished object key (string)
scanObjectValue // just finished non-last object value
scanEndObject // end object (implies scanObjectValue if possible)
scanBeginArray // begin array
scanArrayValue // just finished array value
scanEndArray // end array (implies scanArrayValue if possible)
scanSkipSpace // space byte; can skip; known to be last "continue" result
// Stop.
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
scanError // hit an error, scanner.err.
)
// These values are stored in the parseState stack.
// They give the current state of a composite value
// being scanned. If the parser is inside a nested value
// the parseState describes the nested state, outermost at entry 0.
const (
parseObjectKey = iota // parsing object key (before colon)
parseObjectValue // parsing object value (after colon)
parseArrayValue // parsing array value
)
// This limits the max nesting depth to prevent stack overflow.
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
const maxNestingDepth = 10000
// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
s.step = stateBeginValue
s.parseState = s.parseState[0:0]
s.err = nil
s.endTop = false
}
// eof tells the scanner that the end of input has been reached.
// It returns a scan status just as s.step does.
func (s *scanner) eof() int {
if s.err != nil {
return scanError
}
if s.endTop {
return scanEnd
}
s.step(s, ' ')
if s.endTop {
return scanEnd
}
if s.err == nil {
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
}
return scanError
}
// pushParseState pushes a new parse state p onto the parse stack.
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
s.parseState = append(s.parseState, newParseState)
if len(s.parseState) <= maxNestingDepth {
return successState
}
return s.error(c, "exceeded max depth")
}
// popParseState pops a parse state (already obtained) off the stack
// and updates s.step accordingly.
func (s *scanner) popParseState() {
n := len(s.parseState) - 1
s.parseState = s.parseState[0:n]
if n == 0 {
s.step = stateEndTop
s.endTop = true
} else {
s.step = stateEndValue
}
}
func isSpace(c byte) bool {
return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')
}
// stateBeginValueOrEmpty is the state after reading `[`.
func stateBeginValueOrEmpty(s *scanner, c byte) int {
if isSpace(c) {
return scanSkipSpace
}
if c == ']' {
return stateEndValue(s, c)
}
return stateBeginValue(s, c)
}
// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
if isSpace(c) {
return scanSkipSpace
}
switch c {
case '{':
s.step = stateBeginStringOrEmpty
return s.pushParseState(c, parseObjectKey, scanBeginObject)
case '[':
s.step = stateBeginValueOrEmpty
return s.pushParseState(c, parseArrayValue, scanBeginArray)
case '"':
s.step = stateInString
return scanBeginLiteral
case '-':
s.step = stateNeg
return scanBeginLiteral
case '0': // beginning of 0.123
s.step = state0
return scanBeginLiteral
case 't': // beginning of true
s.step = stateT
return scanBeginLiteral
case 'f': // beginning of false
s.step = stateF
return scanBeginLiteral
case 'n': // beginning of null
s.step = stateN
return scanBeginLiteral
}
if '1' <= c && c <= '9' { // beginning of 1234.5
s.step = state1
return scanBeginLiteral
}
return s.error(c, "looking for beginning of value")
}
// stateBeginStringOrEmpty is the state after reading `{`.
func stateBeginStringOrEmpty(s *scanner, c byte) int {
if isSpace(c) {
return scanSkipSpace
}
if c == '}' {
n := len(s.parseState)
s.parseState[n-1] = parseObjectValue
return stateEndValue(s, c)
}
return stateBeginString(s, c)
}
// stateBeginString is the state after reading `{"key": value,`.
func stateBeginString(s *scanner, c byte) int {
if isSpace(c) {
return scanSkipSpace
}
if c == '"' {
s.step = stateInString
return scanBeginLiteral
}
return s.error(c, "looking for beginning of object key string")
}
// stateEndValue is the state after completing a value,
// such as after reading `{}` or `true` or `["x"`.
func stateEndValue(s *scanner, c byte) int {
n := len(s.parseState)
if n == 0 {
// Completed top-level before the current byte.
s.step = stateEndTop
s.endTop = true
return stateEndTop(s, c)
}
if isSpace(c) {
s.step = stateEndValue
return scanSkipSpace
}
ps := s.parseState[n-1]
switch ps {
case parseObjectKey:
if c == ':' {
s.parseState[n-1] = parseObjectValue
s.step = stateBeginValue
return scanObjectKey
}
return s.error(c, "after object key")
case parseObjectValue:
if c == ',' {
s.parseState[n-1] = parseObjectKey
s.step = stateBeginString
return scanObjectValue
}
if c == '}' {
s.popParseState()
return scanEndObject
}
return s.error(c, "after object key:value pair")
case parseArrayValue:
if c == ',' {
s.step = stateBeginValue
return scanArrayValue
}
if c == ']' {
s.popParseState()
return scanEndArray
}
return s.error(c, "after array element")
}
return s.error(c, "")
}
// stateEndTop is the state after finishing the top-level value,
// such as after reading `{}` or `[1,2,3]`.
// Only space characters should be seen now.
func stateEndTop(s *scanner, c byte) int {
if !isSpace(c) {
// Complain about non-space byte on next call.
s.error(c, "after top-level value")
}
return scanEnd
}
// stateInString is the state after reading `"`.
func stateInString(s *scanner, c byte) int {
if c == '"' {
s.step = stateEndValue
return scanContinue
}
if c == '\\' {
s.step = stateInStringEsc
return scanContinue
}
if c < 0x20 {
return s.error(c, "in string literal")
}
return scanContinue
}
// stateInStringEsc is the state after reading `"\` during a quoted string.
func stateInStringEsc(s *scanner, c byte) int {
switch c {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
s.step = stateInString
return scanContinue
case 'u':
s.step = stateInStringEscU
return scanContinue
}
return s.error(c, "in string escape code")
}
// stateInStringEscU is the state after reading `"\u` during a quoted string.
func stateInStringEscU(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU1
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
func stateInStringEscU1(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU12
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
func stateInStringEscU12(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU123
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
func stateInStringEscU123(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInString
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateNeg is the state after reading `-` during a number.
func stateNeg(s *scanner, c byte) int {
if c == '0' {
s.step = state0
return scanContinue
}
if '1' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return s.error(c, "in numeric literal")
}
// state1 is the state after reading a non-zero integer during a number,
// such as after reading `1` or `100` but not `0`.
func state1(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return state0(s, c)
}
// state0 is the state after reading `0` during a number.
func state0(s *scanner, c byte) int {
if c == '.' {
s.step = stateDot
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateDot is the state after reading the integer and decimal point in a number,
// such as after reading `1.`.
func stateDot(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateDot0
return scanContinue
}
return s.error(c, "after decimal point in numeric literal")
}
// stateDot0 is the state after reading the integer, decimal point, and subsequent
// digits of a number, such as after reading `3.14`.
func stateDot0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateE is the state after reading the mantissa and e in a number,
// such as after reading `314e` or `0.314e`.
func stateE(s *scanner, c byte) int {
if c == '+' || c == '-' {
s.step = stateESign
return scanContinue
}
return stateESign(s, c)
}
// stateESign is the state after reading the mantissa, e, and sign in a number,
// such as after reading `314e-` or `0.314e+`.
func stateESign(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateE0
return scanContinue
}
return s.error(c, "in exponent of numeric literal")
}
// stateE0 is the state after reading the mantissa, e, optional sign,
// and at least one digit of the exponent in a number,
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
func stateE0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
return stateEndValue(s, c)
}
// stateT is the state after reading `t`.
func stateT(s *scanner, c byte) int {
if c == 'r' {
s.step = stateTr
return scanContinue
}
return s.error(c, "in literal true (expecting 'r')")
}
// stateTr is the state after reading `tr`.
func stateTr(s *scanner, c byte) int {
if c == 'u' {
s.step = stateTru
return scanContinue
}
return s.error(c, "in literal true (expecting 'u')")
}
// stateTru is the state after reading `tru`.
func stateTru(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal true (expecting 'e')")
}
// stateF is the state after reading `f`.
func stateF(s *scanner, c byte) int {
if c == 'a' {
s.step = stateFa
return scanContinue
}
return s.error(c, "in literal false (expecting 'a')")
}
// stateFa is the state after reading `fa`.
func stateFa(s *scanner, c byte) int {
if c == 'l' {
s.step = stateFal
return scanContinue
}
return s.error(c, "in literal false (expecting 'l')")
}
// stateFal is the state after reading `fal`.
func stateFal(s *scanner, c byte) int {
if c == 's' {
s.step = stateFals
return scanContinue
}
return s.error(c, "in literal false (expecting 's')")
}
// stateFals is the state after reading `fals`.
func stateFals(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal false (expecting 'e')")
}
// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
if c == 'u' {
s.step = stateNu
return scanContinue
}
return s.error(c, "in literal null (expecting 'u')")
}
// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
if c == 'l' {
s.step = stateNul
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
if c == 'l' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateError is the state after reaching a syntax error,
// such as after reading `[1}` or `5.1.2`.
func stateError(s *scanner, c byte) int {
return scanError
}
// error records an error and switches to the error state.
func (s *scanner) error(c byte, context string) int {
s.step = stateError
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
return scanError
}
// quoteChar formats c as a quoted character literal.
func quoteChar(c byte) string {
// special cases - different from quoted strings
if c == '\'' {
return `'\''`
}
if c == '"' {
return `'"'`
}
// use quoted string with different quotation marks
s := strconv.Quote(string(c))
return "'" + s[1:len(s)-1] + "'"
}

View File

@@ -0,0 +1,46 @@
package sentinel
import (
"github.com/openai/openai-go/internal/encoding/json/shims"
"reflect"
"sync"
)
type cacheEntry struct {
x any
ptr uintptr
kind reflect.Kind
}
var nullCache sync.Map // map[reflect.Type]cacheEntry
func NewNullSentinel[T any](mk func() T) T {
t := shims.TypeFor[T]()
entry, loaded := nullCache.Load(t) // avoid premature allocation
if !loaded {
x := mk()
ptr := reflect.ValueOf(x).Pointer()
entry, _ = nullCache.LoadOrStore(t, cacheEntry{x, ptr, t.Kind()})
}
return entry.(cacheEntry).x.(T)
}
// for internal use only
func IsValueNull(v reflect.Value) bool {
switch v.Kind() {
case reflect.Map, reflect.Slice:
null, ok := nullCache.Load(v.Type())
return ok && v.Pointer() == null.(cacheEntry).ptr
}
return false
}
func IsNull[T any](v T) bool {
t := shims.TypeFor[T]()
switch t.Kind() {
case reflect.Map, reflect.Slice:
null, ok := nullCache.Load(t)
return ok && reflect.ValueOf(v).Pointer() == null.(cacheEntry).ptr
}
return false
}

View File

@@ -0,0 +1,111 @@
// This package provides shims over Go 1.2{2,3} APIs
// which are missing from Go 1.21, and used by the Go 1.24 encoding/json package.
//
// Inside the vendored package, all shim code has comments that begin look like
// // SHIM(...): ...
package shims
import (
"encoding/base64"
"reflect"
"slices"
)
type OverflowableType struct{ reflect.Type }
func (t OverflowableType) OverflowInt(x int64) bool {
k := t.Kind()
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bitSize := t.Size() * 8
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
return x != trunc
}
panic("reflect: OverflowInt of non-int type " + t.String())
}
func (t OverflowableType) OverflowUint(x uint64) bool {
k := t.Kind()
switch k {
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bitSize := t.Size() * 8
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
return x != trunc
}
panic("reflect: OverflowUint of non-uint type " + t.String())
}
// Original src code from Go 1.23 go/src/reflect/type.go (taken 1/9/25)
/*
func (t *rtype) OverflowInt(x int64) bool {
k := t.Kind()
switch k {
case Int, Int8, Int16, Int32, Int64:
bitSize := t.Size() * 8
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
return x != trunc
}
panic("reflect: OverflowInt of non-int type " + t.String())
}
func (t *rtype) OverflowUint(x uint64) bool {
k := t.Kind()
switch k {
case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64:
bitSize := t.Size() * 8
trunc := (x << (64 - bitSize)) >> (64 - bitSize)
return x != trunc
}
panic("reflect: OverflowUint of non-uint type " + t.String())
}
*/
// TypeFor returns the [Type] that represents the type argument T.
func TypeFor[T any]() reflect.Type {
var v T
if t := reflect.TypeOf(v); t != nil {
return t // optimize for T being a non-interface kind
}
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
}
// Original src code from Go 1.23 go/src/reflect/type.go (taken 1/9/25)
/*
// TypeFor returns the [Type] that represents the type argument T.
func TypeFor[T any]() Type {
var v T
if t := TypeOf(v); t != nil {
return t // optimize for T being a non-interface kind
}
return TypeOf((*T)(nil)).Elem() // only for an interface kind
}
*/
type AppendableStdEncoding struct{ *base64.Encoding }
// AppendEncode appends the base64 encoded src to dst
// and returns the extended buffer.
func (enc AppendableStdEncoding) AppendEncode(dst, src []byte) []byte {
n := enc.EncodedLen(len(src))
dst = slices.Grow(dst, n)
enc.Encode(dst[len(dst):][:n], src)
return dst[:len(dst)+n]
}
// Original src code from Go 1.23.4 go/src/encoding/base64/base64.go (taken 1/9/25)
/*
// AppendEncode appends the base64 encoded src to dst
// and returns the extended buffer.
func (enc *Encoding) AppendEncode(dst, src []byte) []byte {
n := enc.EncodedLen(len(src))
dst = slices.Grow(dst, n)
enc.Encode(dst[len(dst):][:n], src)
return dst[:len(dst)+n]
}
*/

View File

@@ -0,0 +1,512 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import (
"bytes"
"errors"
"io"
)
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
r io.Reader
buf []byte
d decodeState
scanp int // start of unread data in buf
scanned int64 // amount of data already scanned
scan scanner
err error
tokenState int
tokenStack []int
}
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may
// read data from r beyond the JSON values requested.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// UseNumber causes the Decoder to unmarshal a number into an
// interface value as a [Number] instead of as a float64.
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
// DisallowUnknownFields causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
// Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v.
//
// See the documentation for [Unmarshal] for details about
// the conversion of JSON into a Go value.
func (dec *Decoder) Decode(v any) error {
if dec.err != nil {
return dec.err
}
if err := dec.tokenPrepareForDecode(); err != nil {
return err
}
if !dec.tokenValueAllowed() {
return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
}
// Read whole value into buffer.
n, err := dec.readValue()
if err != nil {
return err
}
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
dec.scanp += n
// Don't save err from unmarshal into dec.err:
// the connection is still usable since we read a complete JSON
// object from it before the error happened.
err = dec.d.unmarshal(v)
// fixup token streaming state
dec.tokenValueEnd()
return err
}
// Buffered returns a reader of the data remaining in the Decoder's
// buffer. The reader is valid until the next call to [Decoder.Decode].
func (dec *Decoder) Buffered() io.Reader {
return bytes.NewReader(dec.buf[dec.scanp:])
}
// readValue reads a JSON value into dec.buf.
// It returns the length of the encoding.
func (dec *Decoder) readValue() (int, error) {
dec.scan.reset()
scanp := dec.scanp
var err error
Input:
// help the compiler see that scanp is never negative, so it can remove
// some bounds checks below.
for scanp >= 0 {
// Look in the buffer for a new value.
for ; scanp < len(dec.buf); scanp++ {
c := dec.buf[scanp]
dec.scan.bytes++
switch dec.scan.step(&dec.scan, c) {
case scanEnd:
// scanEnd is delayed one byte so we decrement
// the scanner bytes count by 1 to ensure that
// this value is correct in the next call of Decode.
dec.scan.bytes--
break Input
case scanEndObject, scanEndArray:
// scanEnd is delayed one byte.
// We might block trying to get that byte from src,
// so instead invent a space byte.
if stateEndValue(&dec.scan, ' ') == scanEnd {
scanp++
break Input
}
case scanError:
dec.err = dec.scan.err
return 0, dec.scan.err
}
}
// Did the last read have an error?
// Delayed until now to allow buffer scan.
if err != nil {
if err == io.EOF {
if dec.scan.step(&dec.scan, ' ') == scanEnd {
break Input
}
if nonSpace(dec.buf) {
err = io.ErrUnexpectedEOF
}
}
dec.err = err
return 0, err
}
n := scanp - dec.scanp
err = dec.refill()
scanp = dec.scanp + n
}
return scanp - dec.scanp, nil
}
func (dec *Decoder) refill() error {
// Make room to read more into the buffer.
// First slide down data already consumed.
if dec.scanp > 0 {
dec.scanned += int64(dec.scanp)
n := copy(dec.buf, dec.buf[dec.scanp:])
dec.buf = dec.buf[:n]
dec.scanp = 0
}
// Grow buffer if not large enough.
const minRead = 512
if cap(dec.buf)-len(dec.buf) < minRead {
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
copy(newBuf, dec.buf)
dec.buf = newBuf
}
// Read. Delay error for next iteration (after scan).
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
dec.buf = dec.buf[0 : len(dec.buf)+n]
return err
}
func nonSpace(b []byte) bool {
for _, c := range b {
if !isSpace(c) {
return true
}
}
return false
}
// An Encoder writes JSON values to an output stream.
type Encoder struct {
w io.Writer
err error
escapeHTML bool
indentBuf []byte
indentPrefix string
indentValue string
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w, escapeHTML: true}
}
// Encode writes the JSON encoding of v to the stream,
// with insignificant space characters elided,
// followed by a newline character.
//
// See the documentation for [Marshal] for details about the
// conversion of Go values to JSON.
func (enc *Encoder) Encode(v any) error {
if enc.err != nil {
return enc.err
}
e := newEncodeState()
defer encodeStatePool.Put(e)
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
if err != nil {
return err
}
// Terminate each value with a newline.
// This makes the output look a little nicer
// when debugging, and some kind of space
// is required if the encoded value was a number,
// so that the reader knows there aren't more
// digits coming.
e.WriteByte('\n')
b := e.Bytes()
if enc.indentPrefix != "" || enc.indentValue != "" {
enc.indentBuf, err = appendIndent(enc.indentBuf[:0], b, enc.indentPrefix, enc.indentValue)
if err != nil {
return err
}
b = enc.indentBuf
}
if _, err = enc.w.Write(b); err != nil {
enc.err = err
}
return err
}
// SetIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation.
func (enc *Encoder) SetIndent(prefix, indent string) {
enc.indentPrefix = prefix
enc.indentValue = indent
}
// SetEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
// to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability
// of the output, SetEscapeHTML(false) disables this behavior.
func (enc *Encoder) SetEscapeHTML(on bool) {
enc.escapeHTML = on
}
// RawMessage is a raw encoded JSON value.
// It implements [Marshaler] and [Unmarshaler] and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
var _ Marshaler = (*RawMessage)(nil)
var _ Unmarshaler = (*RawMessage)(nil)
// A Token holds a value of one of these types:
//
// - [Delim], for the four JSON delimiters [ ] { }
// - bool, for JSON booleans
// - float64, for JSON numbers
// - [Number], for JSON numbers
// - string, for JSON string literals
// - nil, for JSON null
type Token any
const (
tokenTopValue = iota
tokenArrayStart
tokenArrayValue
tokenArrayComma
tokenObjectStart
tokenObjectKey
tokenObjectColon
tokenObjectValue
tokenObjectComma
)
// advance tokenstate from a separator state to a value state
func (dec *Decoder) tokenPrepareForDecode() error {
// Note: Not calling peek before switch, to avoid
// putting peek into the standard Decode path.
// peek is only called when using the Token API.
switch dec.tokenState {
case tokenArrayComma:
c, err := dec.peek()
if err != nil {
return err
}
if c != ',' {
return &SyntaxError{"expected comma after array element", dec.InputOffset()}
}
dec.scanp++
dec.tokenState = tokenArrayValue
case tokenObjectColon:
c, err := dec.peek()
if err != nil {
return err
}
if c != ':' {
return &SyntaxError{"expected colon after object key", dec.InputOffset()}
}
dec.scanp++
dec.tokenState = tokenObjectValue
}
return nil
}
func (dec *Decoder) tokenValueAllowed() bool {
switch dec.tokenState {
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
return true
}
return false
}
func (dec *Decoder) tokenValueEnd() {
switch dec.tokenState {
case tokenArrayStart, tokenArrayValue:
dec.tokenState = tokenArrayComma
case tokenObjectValue:
dec.tokenState = tokenObjectComma
}
}
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
type Delim rune
func (d Delim) String() string {
return string(d)
}
// Token returns the next JSON token in the input stream.
// At the end of the input stream, Token returns nil, [io.EOF].
//
// Token guarantees that the delimiters [ ] { } it returns are
// properly nested and matched: if Token encounters an unexpected
// delimiter in the input, it will return an error.
//
// The input stream consists of basic JSON values—bool, string,
// number, and null—along with delimiters [ ] { } of type [Delim]
// to mark the start and end of arrays and objects.
// Commas and colons are elided.
func (dec *Decoder) Token() (Token, error) {
for {
c, err := dec.peek()
if err != nil {
return nil, err
}
switch c {
case '[':
if !dec.tokenValueAllowed() {
return dec.tokenError(c)
}
dec.scanp++
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
dec.tokenState = tokenArrayStart
return Delim('['), nil
case ']':
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
return dec.tokenError(c)
}
dec.scanp++
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
dec.tokenValueEnd()
return Delim(']'), nil
case '{':
if !dec.tokenValueAllowed() {
return dec.tokenError(c)
}
dec.scanp++
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
dec.tokenState = tokenObjectStart
return Delim('{'), nil
case '}':
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
return dec.tokenError(c)
}
dec.scanp++
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
dec.tokenValueEnd()
return Delim('}'), nil
case ':':
if dec.tokenState != tokenObjectColon {
return dec.tokenError(c)
}
dec.scanp++
dec.tokenState = tokenObjectValue
continue
case ',':
if dec.tokenState == tokenArrayComma {
dec.scanp++
dec.tokenState = tokenArrayValue
continue
}
if dec.tokenState == tokenObjectComma {
dec.scanp++
dec.tokenState = tokenObjectKey
continue
}
return dec.tokenError(c)
case '"':
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
var x string
old := dec.tokenState
dec.tokenState = tokenTopValue
err := dec.Decode(&x)
dec.tokenState = old
if err != nil {
return nil, err
}
dec.tokenState = tokenObjectColon
return x, nil
}
fallthrough
default:
if !dec.tokenValueAllowed() {
return dec.tokenError(c)
}
var x any
if err := dec.Decode(&x); err != nil {
return nil, err
}
return x, nil
}
}
}
func (dec *Decoder) tokenError(c byte) (Token, error) {
var context string
switch dec.tokenState {
case tokenTopValue:
context = " looking for beginning of value"
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
context = " looking for beginning of value"
case tokenArrayComma:
context = " after array element"
case tokenObjectKey:
context = " looking for beginning of object key string"
case tokenObjectColon:
context = " after object key"
case tokenObjectComma:
context = " after object key:value pair"
}
return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.InputOffset()}
}
// More reports whether there is another element in the
// current array or object being parsed.
func (dec *Decoder) More() bool {
c, err := dec.peek()
return err == nil && c != ']' && c != '}'
}
func (dec *Decoder) peek() (byte, error) {
var err error
for {
for i := dec.scanp; i < len(dec.buf); i++ {
c := dec.buf[i]
if isSpace(c) {
continue
}
dec.scanp = i
return c, nil
}
// buffer has been scanned, now report any error
if err != nil {
return 0, err
}
err = dec.refill()
}
}
// InputOffset returns the input stream byte offset of the current decoder position.
// The offset gives the location of the end of the most recently returned token
// and the beginning of the next token.
func (dec *Decoder) InputOffset() int64 {
return dec.scanned + int64(dec.scanp)
}

View File

@@ -0,0 +1,218 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import "unicode/utf8"
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}
// htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': false,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': false,
'=': true,
'>': false,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}

View File

@@ -0,0 +1,38 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import (
"strings"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
tag, opt, _ := strings.Cut(tag, ",")
return tag, tagOptions(opt)
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var name string
name, s, _ = strings.Cut(s, ",")
if name == optionName {
return true
}
}
return false
}

View File

@@ -0,0 +1,61 @@
// EDIT(begin): custom time marshaler
package json
import (
"github.com/openai/openai-go/internal/encoding/json/shims"
"reflect"
"time"
)
type TimeMarshaler interface {
MarshalJSONWithTimeLayout(string) []byte
}
func TimeLayout(fmt string) string {
switch fmt {
case "", "date-time":
return time.RFC3339
case "date":
return time.DateOnly
default:
return fmt
}
}
var timeType = shims.TypeFor[time.Time]()
func newTimeEncoder() encoderFunc {
return func(e *encodeState, v reflect.Value, opts encOpts) {
t := v.Interface().(time.Time)
fmtted := t.Format(TimeLayout(opts.timefmt))
stringEncoder(e, reflect.ValueOf(fmtted), opts)
}
}
// Uses continuation passing style, to add the timefmt option to k
func continueWithTimeFmt(timefmt string, k encoderFunc) encoderFunc {
return func(e *encodeState, v reflect.Value, opts encOpts) {
opts.timefmt = timefmt
k(e, v, opts)
}
}
func timeMarshalEncoder(e *encodeState, v reflect.Value, opts encOpts) bool {
tm, ok := v.Interface().(TimeMarshaler)
if !ok {
return false
}
b := tm.MarshalJSONWithTimeLayout(opts.timefmt)
if b != nil {
e.Grow(len(b))
out := e.AvailableBuffer()
out, _ = appendCompact(out, b, opts.escapeHTML)
e.Buffer.Write(out)
return true
}
return false
}
// EDIT(end)

View File

@@ -0,0 +1,30 @@
package paramutil
import (
"github.com/openai/openai-go/packages/param"
"github.com/openai/openai-go/packages/respjson"
)
func AddrIfPresent[T comparable](v param.Opt[T]) *T {
if v.Valid() {
return &v.Value
}
return nil
}
func ToOpt[T comparable](v T, meta respjson.Field) param.Opt[T] {
if meta.Valid() {
return param.NewOpt(v)
} else if meta.Raw() == respjson.Null {
return param.Null[T]()
}
return param.Opt[T]{}
}
// Checks if the value is not omitted and not null
func Valid(v param.ParamStruct) bool {
if ovr, ok := v.Overrides(); ok {
return ovr != nil
}
return !param.IsNull(v) && !param.IsOmitted(v)
}

View File

@@ -0,0 +1,48 @@
package paramutil
import (
"fmt"
"github.com/openai/openai-go/packages/param"
"reflect"
)
var paramUnionType = reflect.TypeOf(param.APIUnion{})
// VariantFromUnion can be used to extract the present variant from a param union type.
// A param union type is a struct with an embedded field of [APIUnion].
func VariantFromUnion(u reflect.Value) (any, error) {
if u.Kind() == reflect.Ptr {
u = u.Elem()
}
if u.Kind() != reflect.Struct {
return nil, fmt.Errorf("param: cannot extract variant from non-struct union")
}
isUnion := false
nVariants := 0
variantIdx := -1
for i := 0; i < u.NumField(); i++ {
if !u.Field(i).IsZero() {
nVariants++
variantIdx = i
}
if u.Field(i).Type() == paramUnionType {
isUnion = u.Type().Field(i).Anonymous
}
}
if !isUnion {
return nil, fmt.Errorf("param: cannot extract variant from non-union")
}
if nVariants > 1 {
return nil, fmt.Errorf("param: cannot extract variant from union with multiple variants")
}
if nVariants == 0 {
return nil, fmt.Errorf("param: cannot extract variant from union with no variants")
}
return u.Field(variantIdx).Interface(), nil
}

View File

@@ -0,0 +1,635 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package requestconfig
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math"
"math/rand"
"mime"
"net/http"
"net/url"
"runtime"
"strconv"
"strings"
"time"
"github.com/openai/openai-go/internal"
"github.com/openai/openai-go/internal/apierror"
"github.com/openai/openai-go/internal/apiform"
"github.com/openai/openai-go/internal/apiquery"
"github.com/tidwall/gjson"
)
func getDefaultHeaders() map[string]string {
return map[string]string{
"User-Agent": fmt.Sprintf("OpenAI/Go %s", internal.PackageVersion),
}
}
func getNormalizedOS() string {
switch runtime.GOOS {
case "ios":
return "iOS"
case "android":
return "Android"
case "darwin":
return "MacOS"
case "window":
return "Windows"
case "freebsd":
return "FreeBSD"
case "openbsd":
return "OpenBSD"
case "linux":
return "Linux"
default:
return fmt.Sprintf("Other:%s", runtime.GOOS)
}
}
func getNormalizedArchitecture() string {
switch runtime.GOARCH {
case "386":
return "x32"
case "amd64":
return "x64"
case "arm":
return "arm"
case "arm64":
return "arm64"
default:
return fmt.Sprintf("other:%s", runtime.GOARCH)
}
}
func getPlatformProperties() map[string]string {
return map[string]string{
"X-Stainless-Lang": "go",
"X-Stainless-Package-Version": internal.PackageVersion,
"X-Stainless-OS": getNormalizedOS(),
"X-Stainless-Arch": getNormalizedArchitecture(),
"X-Stainless-Runtime": "go",
"X-Stainless-Runtime-Version": runtime.Version(),
}
}
type RequestOption interface {
Apply(*RequestConfig) error
}
type RequestOptionFunc func(*RequestConfig) error
type PreRequestOptionFunc func(*RequestConfig) error
func (s RequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
func NewRequestConfig(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) (*RequestConfig, error) {
var reader io.Reader
contentType := "application/json"
hasSerializationFunc := false
if body, ok := body.(json.Marshaler); ok {
content, err := body.MarshalJSON()
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(content)
hasSerializationFunc = true
}
if body, ok := body.(apiform.Marshaler); ok {
var (
content []byte
err error
)
content, contentType, err = body.MarshalMultipart()
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(content)
hasSerializationFunc = true
}
if body, ok := body.(apiquery.Queryer); ok {
hasSerializationFunc = true
q, err := body.URLQuery()
if err != nil {
return nil, err
}
params := q.Encode()
if params != "" {
u = u + "?" + params
}
}
if body, ok := body.([]byte); ok {
reader = bytes.NewBuffer(body)
hasSerializationFunc = true
}
if body, ok := body.(io.Reader); ok {
reader = body
hasSerializationFunc = true
}
// Fallback to json serialization if none of the serialization functions that we expect
// to see is present.
if body != nil && !hasSerializationFunc {
content, err := json.Marshal(body)
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(content)
}
req, err := http.NewRequestWithContext(ctx, method, u, nil)
if err != nil {
return nil, err
}
if reader != nil {
req.Header.Set("Content-Type", contentType)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Stainless-Retry-Count", "0")
req.Header.Set("X-Stainless-Timeout", "0")
for k, v := range getDefaultHeaders() {
req.Header.Add(k, v)
}
for k, v := range getPlatformProperties() {
req.Header.Add(k, v)
}
cfg := RequestConfig{
MaxRetries: 2,
Context: ctx,
Request: req,
HTTPClient: http.DefaultClient,
Body: reader,
}
cfg.ResponseBodyInto = dst
err = cfg.Apply(opts...)
if err != nil {
return nil, err
}
// This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only
// apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified
// by the user and we should respect that.
if req.Header.Get("X-Stainless-Timeout") == "0" {
if cfg.RequestTimeout == time.Duration(0) {
req.Header.Del("X-Stainless-Timeout")
} else {
req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds())))
}
}
return &cfg, nil
}
// This interface is primarily used to describe an [*http.Client], but also
// supports custom HTTP implementations.
type HTTPDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// RequestConfig represents all the state related to one request.
//
// Editing the variables inside RequestConfig directly is unstable api. Prefer
// composing the RequestOption instead if possible.
type RequestConfig struct {
MaxRetries int
RequestTimeout time.Duration
Context context.Context
Request *http.Request
BaseURL *url.URL
// DefaultBaseURL will be used if BaseURL is not explicitly overridden using
// WithBaseURL.
DefaultBaseURL *url.URL
CustomHTTPDoer HTTPDoer
HTTPClient *http.Client
Middlewares []middleware
APIKey string
Organization string
Project string
WebhookSecret string
// If ResponseBodyInto not nil, then we will attempt to deserialize into
// ResponseBodyInto. If Destination is a []byte, then it will return the body as
// is.
ResponseBodyInto any
// ResponseInto copies the \*http.Response of the corresponding request into the
// given address
ResponseInto **http.Response
Body io.Reader
}
// middleware is exactly the same type as the Middleware type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middleware = func(*http.Request, middlewareNext) (*http.Response, error)
// middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middlewareNext = func(*http.Request) (*http.Response, error)
func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext {
return func(req *http.Request) (res *http.Response, err error) {
return middleware(req, next)
}
}
func shouldRetry(req *http.Request, res *http.Response) bool {
// If there is no way to recover the Body, then we shouldn't retry.
if req.Body != nil && req.GetBody == nil {
return false
}
// If there is no response, that indicates that there is a connection error
// so we retry the request.
if res == nil {
return true
}
// If the header explicitly wants a retry behavior, respect that over the
// http status code.
if res.Header.Get("x-should-retry") == "true" {
return true
}
if res.Header.Get("x-should-retry") == "false" {
return false
}
return res.StatusCode == http.StatusRequestTimeout ||
res.StatusCode == http.StatusConflict ||
res.StatusCode == http.StatusTooManyRequests ||
res.StatusCode >= http.StatusInternalServerError
}
func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) {
if resp == nil {
return 0, false
}
type retryData struct {
header string
units time.Duration
// custom is used when the regular algorithm failed and is optional.
// the returned duration is used verbatim (units is not applied).
custom func(string) (time.Duration, bool)
}
nop := func(string) (time.Duration, bool) { return 0, false }
// the headers are listed in order of preference
retries := []retryData{
{
header: "Retry-After-Ms",
units: time.Millisecond,
custom: nop,
},
{
header: "Retry-After",
units: time.Second,
// retry-after values are expressed in either number of
// seconds or an HTTP-date indicating when to try again
custom: func(ra string) (time.Duration, bool) {
t, err := time.Parse(time.RFC1123, ra)
if err != nil {
return 0, false
}
return time.Until(t), true
},
},
}
for _, retry := range retries {
v := resp.Header.Get(retry.header)
if v == "" {
continue
}
if retryAfter, err := strconv.ParseFloat(v, 64); err == nil {
return time.Duration(retryAfter * float64(retry.units)), true
}
if d, ok := retry.custom(v); ok {
return d, true
}
}
return 0, false
}
// isBeforeContextDeadline reports whether the non-zero Time t is
// before ctx's deadline. If ctx does not have a deadline, it
// always reports true (the deadline is considered infinite).
func isBeforeContextDeadline(t time.Time, ctx context.Context) bool {
d, ok := ctx.Deadline()
if !ok {
return true
}
return t.Before(d)
}
// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func
// to handle timeouts etc. It wraps an existing io.ReadCloser.
type bodyWithTimeout struct {
stop func() // stops the time.Timer waiting to cancel the request
rc io.ReadCloser
}
func (b *bodyWithTimeout) Read(p []byte) (n int, err error) {
n, err = b.rc.Read(p)
if err == nil {
return n, nil
}
if err == io.EOF {
return n, err
}
return n, err
}
func (b *bodyWithTimeout) Close() error {
err := b.rc.Close()
b.stop()
return err
}
func retryDelay(res *http.Response, retryCount int) time.Duration {
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says.
if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute {
return retryAfterDelay
}
maxDelay := 8 * time.Second
delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount)))
if delay > maxDelay {
delay = maxDelay
}
jitter := rand.Int63n(int64(delay / 4))
delay -= time.Duration(jitter)
return delay
}
func (cfg *RequestConfig) Execute() (err error) {
if cfg.BaseURL == nil {
if cfg.DefaultBaseURL != nil {
cfg.BaseURL = cfg.DefaultBaseURL
} else {
return fmt.Errorf("requestconfig: base url is not set")
}
}
cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/"))
if err != nil {
return err
}
if cfg.Body != nil && cfg.Request.Body == nil {
switch body := cfg.Body.(type) {
case *bytes.Buffer:
b := body.Bytes()
cfg.Request.ContentLength = int64(body.Len())
cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }
cfg.Request.Body, _ = cfg.Request.GetBody()
case *bytes.Reader:
cfg.Request.ContentLength = int64(body.Len())
cfg.Request.GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, 0)
return io.NopCloser(body), err
}
cfg.Request.Body, _ = cfg.Request.GetBody()
default:
if rc, ok := body.(io.ReadCloser); ok {
cfg.Request.Body = rc
} else {
cfg.Request.Body = io.NopCloser(body)
}
}
}
handler := cfg.HTTPClient.Do
if cfg.CustomHTTPDoer != nil {
handler = cfg.CustomHTTPDoer.Do
}
for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 {
handler = applyMiddleware(cfg.Middlewares[i], handler)
}
// Don't send the current retry count in the headers if the caller modified the header defaults.
shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0"
var res *http.Response
var cancel context.CancelFunc
for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 {
ctx := cfg.Request.Context()
if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) {
ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout)
defer func() {
// The cancel function is nil if it was handed off to be handled in a different scope.
if cancel != nil {
cancel()
}
}()
}
req := cfg.Request.Clone(ctx)
if shouldSendRetryCount {
req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount))
}
res, err = handler(req)
if ctx != nil && ctx.Err() != nil {
return ctx.Err()
}
if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries {
break
}
// Prepare next request and wait for the retry delay
if cfg.Request.GetBody != nil {
cfg.Request.Body, err = cfg.Request.GetBody()
if err != nil {
return err
}
}
// Can't actually refresh the body, so we don't attempt to retry here
if cfg.Request.GetBody == nil && cfg.Request.Body != nil {
break
}
time.Sleep(retryDelay(res, retryCount))
}
// Save *http.Response if it is requested to, even if there was an error making the request. This is
// useful in cases where you might want to debug by inspecting the response. Note that if err != nil,
// the response should be generally be empty, but there are edge cases.
if cfg.ResponseInto != nil {
*cfg.ResponseInto = res
}
if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok {
*responseBodyInto = res
}
// If there was a connection error in the final request or any other transport error,
// return that early without trying to coerce into an APIError.
if err != nil {
return err
}
if res.StatusCode >= 400 {
contents, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return err
}
// If there is an APIError, re-populate the response body so that debugging
// utilities can conveniently dump the response without issue.
res.Body = io.NopCloser(bytes.NewBuffer(contents))
// Load the contents into the error format if it is provided.
aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode}
unwrapped := gjson.GetBytes(contents, "error").Raw
err = aerr.UnmarshalJSON([]byte(unwrapped))
if err != nil {
return err
}
return &aerr
}
_, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response)
if cfg.ResponseBodyInto == nil || intoCustomResponseBody {
// We aren't reading the response body in this scope, but whoever is will need the
// cancel func from the context to observe request timeouts.
// Put the cancel function in the response body so it can be handled elsewhere.
if cancel != nil {
res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel}
cancel = nil
}
return nil
}
contents, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return fmt.Errorf("error reading response body: %w", err)
}
// If we are not json, return plaintext
contentType := res.Header.Get("content-type")
mediaType, _, _ := mime.ParseMediaType(contentType)
isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json")
if !isJSON {
switch dst := cfg.ResponseBodyInto.(type) {
case *string:
*dst = string(contents)
case **string:
tmp := string(contents)
*dst = &tmp
case *[]byte:
*dst = contents
default:
return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType)
}
return nil
}
switch dst := cfg.ResponseBodyInto.(type) {
// If the response happens to be a byte array, deserialize the body as-is.
case *[]byte:
*dst = contents
default:
err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto)
if err != nil {
return fmt.Errorf("error parsing response json: %w", err)
}
}
return nil
}
func ExecuteNewRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error {
cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...)
if err != nil {
return err
}
return cfg.Execute()
}
func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
if cfg == nil {
return nil
}
req := cfg.Request.Clone(ctx)
var err error
if req.Body != nil {
req.Body, err = req.GetBody()
}
if err != nil {
return nil
}
new := &RequestConfig{
MaxRetries: cfg.MaxRetries,
RequestTimeout: cfg.RequestTimeout,
Context: ctx,
Request: req,
BaseURL: cfg.BaseURL,
HTTPClient: cfg.HTTPClient,
Middlewares: cfg.Middlewares,
APIKey: cfg.APIKey,
Organization: cfg.Organization,
Project: cfg.Project,
WebhookSecret: cfg.WebhookSecret,
}
return new
}
func (cfg *RequestConfig) Apply(opts ...RequestOption) error {
for _, opt := range opts {
err := opt.Apply(cfg)
if err != nil {
return err
}
}
return nil
}
// PreRequestOptions is used to collect all the options which need to be known before
// a call to [RequestConfig.ExecuteNewRequest], such as path parameters
// or global defaults.
// PreRequestOptions will return a [RequestConfig] with the options applied.
//
// Only request option functions of type [PreRequestOptionFunc] are applied.
func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) {
cfg := RequestConfig{}
for _, opt := range opts {
if opt, ok := opt.(PreRequestOptionFunc); ok {
err := opt.Apply(&cfg)
if err != nil {
return cfg, err
}
}
}
return cfg, nil
}
// WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL.
// This is always overridden by setting a base URL with WithBaseURL.
// WithBaseURL should be used instead of WithDefaultBaseURL except in internal code.
func WithDefaultBaseURL(baseURL string) RequestOption {
u, err := url.Parse(baseURL)
return RequestOptionFunc(func(r *RequestConfig) error {
if err != nil {
return err
}
r.DefaultBaseURL = u
return nil
})
}

View File

@@ -0,0 +1,5 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
package internal
const PackageVersion = "1.12.0" // x-release-please-version