openai/openai-go

Public

mirrored fromhttps://github.com/openai/openai-goAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
9314b6c3bd913e265f75d0a49600b60d6d25d8ae

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/param/encoder_test.go

553lines · modecode

1package param_test
2
3import (
4 "bytes"
5 "encoding/json"
6 "reflect"
7 "testing"
8 "time"
9
10 shimjson "github.com/openai/openai-go/v3/internal/encoding/json"
11 "github.com/openai/openai-go/v3/packages/param"
12)
13
14type Struct struct {
15 A string `json:"a"`
16 B int64 `json:"b"`
17 param.APIObject
18}
19
20func (r Struct) MarshalJSON() (data []byte, err error) {
21 type shadow Struct
22 return param.MarshalObject(r, (*shadow)(&r))
23}
24
25// Note that the order of fields affects the JSON
26// key order. Changing the order of the fields in this struct
27// will fail tests unnecessarily.
28type FieldStruct struct {
29 A param.Opt[string] `json:"a,omitzero"`
30 B param.Opt[int64] `json:"b,omitzero"`
31 C Struct `json:"c,omitzero"`
32 D time.Time `json:"d,omitzero" format:"date"`
33 E time.Time `json:"e,omitzero"`
34 F param.Opt[time.Time] `json:"f,omitzero" format:"date"`
35 G param.Opt[time.Time] `json:"g,omitzero"`
36 H param.Opt[time.Time] `json:"h,omitzero" format:"date-time"`
37 param.APIObject
38}
39
40func (r FieldStruct) MarshalJSON() (data []byte, err error) {
41 type shadow FieldStruct
42 return param.MarshalObject(r, (*shadow)(&r))
43}
44
45type StructWithAdditionalProperties struct {
46 First string `json:"first"`
47 Second int `json:"second"`
48 ExtraFields map[string]any `json:"-"`
49 param.APIObject
50}
51
52func (s StructWithAdditionalProperties) MarshalJSON() ([]byte, error) {
53 type shadow StructWithAdditionalProperties
54 return param.MarshalWithExtras(s, (*shadow)(&s), s.ExtraFields)
55}
56
57func TestIsNullish(t *testing.T) {
58 nullTests := map[string]param.ParamNullable{
59 "null_string": param.Null[string](),
60 "null_int64": param.Null[int64](),
61 "null_time": param.Null[time.Time](),
62 "null_struct": param.NullStruct[Struct](),
63 }
64
65 for name, test := range nullTests {
66 t.Run(name, func(t *testing.T) {
67 if !param.IsNull(test) {
68 t.Fatalf("expected %s to be null", name)
69 }
70 if param.IsOmitted(test) {
71 t.Fatalf("expected %s to not be omitted", name)
72 }
73 })
74 }
75
76 omitTests := map[string]param.ParamNullable{
77 "omit_string": param.Opt[string]{},
78 "omit_int64": param.Opt[int64]{},
79 "omit_time": param.Opt[time.Time]{},
80 "omit_struct": Struct{},
81 }
82
83 for name, test := range omitTests {
84 t.Run(name, func(t *testing.T) {
85 if param.IsNull(test) {
86 t.Fatalf("expected %s to be null", name)
87 }
88 if !param.IsOmitted(test) {
89 t.Fatalf("expected %s to not be omitted", name)
90 }
91 })
92 }
93}
94
95func TestFieldMarshal(t *testing.T) {
96 tests := map[string]struct {
97 value any
98 expected string
99 }{
100 "null_string": {param.Null[string](), "null"},
101 "null_int64": {param.Null[int64](), "null"},
102 "null_time": {param.Null[time.Time](), "null"},
103 "null_struct": {param.NullStruct[Struct](), "null"},
104
105 "float_zero": {param.NewOpt(float64(0.0)), "0"},
106 "string_zero": {param.NewOpt(""), `""`},
107 "time_zero": {param.NewOpt(time.Time{}), `"0001-01-01T00:00:00Z"`},
108
109 "string": {param.Opt[string]{Value: "string"}, `"string"`},
110 "int": {param.Opt[int64]{Value: 123}, "123"},
111 "int64": {param.Opt[int64]{Value: int64(123456789123456789)}, "123456789123456789"},
112 "struct": {Struct{A: "yo", B: 123}, `{"a":"yo","b":123}`},
113 "datetime": {
114 param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
115 `"2023-03-18T14:47:38Z"`,
116 },
117 "optional_date": {
118 FieldStruct{
119 F: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
120 },
121 `{"f":"2023-03-18"}`,
122 },
123 "optional_time": {
124 FieldStruct{
125 G: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
126 },
127 `{"g":"2023-03-18T14:47:38Z"}`,
128 },
129 "optional_datetime_explicit_format": {
130 FieldStruct{
131 H: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
132 },
133 `{"h":"2023-03-18T14:47:38Z"}`,
134 },
135 "param_struct": {
136 FieldStruct{
137 A: param.Opt[string]{Value: "hello"},
138 B: param.Opt[int64]{Value: int64(12)},
139 D: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC),
140 E: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC),
141 F: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
142 G: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
143 H: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
144 },
145 `{"a":"hello","b":12,"d":"2023-03-18","e":"2023-03-18T14:47:38Z","f":"2023-03-18","g":"2023-03-18T14:47:38Z","h":"2023-03-18T14:47:38Z"}`,
146 },
147 }
148
149 for name, test := range tests {
150 t.Run(name, func(t *testing.T) {
151 b, err := json.Marshal(test.value)
152 if err != nil {
153 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
154 }
155 if string(b) != test.expected {
156 t.Fatalf("expected %s, received %s", test.expected, string(b))
157 }
158 })
159 }
160}
161
162func TestAdditionalProperties(t *testing.T) {
163 s := StructWithAdditionalProperties{
164 First: "hello",
165 Second: 14,
166 ExtraFields: map[string]any{
167 "hi": "there",
168 },
169 }
170 exp := `{"first":"hello","second":14,"hi":"there"}`
171
172 bytes, err := json.Marshal(s)
173 if err != nil {
174 t.Fatalf("failed to marshal: %v", err)
175 }
176
177 if string(bytes) != exp {
178 t.Fatalf("expected %s, got %s", exp, string(bytes))
179 }
180}
181
182func TestExtraFields(t *testing.T) {
183 v := Struct{
184 A: "hello",
185 B: 123,
186 }
187 v.SetExtraFields(map[string]any{
188 "extra": Struct{A: "recursive"},
189 "b": nil,
190 })
191 bytes, err := json.Marshal(v)
192 if err != nil {
193 t.Fatalf("failed to marshal: %v", err)
194 }
195 if string(bytes) != `{"a":"hello","b":null,"extra":{"a":"recursive","b":0}}` {
196 t.Fatalf("failed to marshal: got %v", string(bytes))
197 }
198 if v.B != 123 {
199 t.Fatalf("marshal modified field B: got %v", v.B)
200 }
201}
202
203func TestExtraFieldsForceOmitted(t *testing.T) {
204 v := Struct{
205 // Testing with the zero value.
206 // A: "",
207 // B: 0,
208 }
209 v.SetExtraFields(map[string]any{
210 "b": param.Omit,
211 })
212 bytes, err := json.Marshal(v)
213 if err != nil {
214 t.Fatalf("failed to marshal: %v", err)
215 }
216 if string(bytes) != `{"a":""}` {
217 t.Fatalf("failed to marshal: got %v", string(bytes))
218 }
219}
220
221type UnionWithDates struct {
222 OfDate param.Opt[time.Time]
223 OfTime param.Opt[time.Time]
224 param.APIUnion
225}
226
227func (r UnionWithDates) MarshalJSON() (data []byte, err error) {
228 return param.MarshalUnion(r, param.EncodedAsDate(r.OfDate), r.OfTime)
229}
230
231func TestUnionDateMarshal(t *testing.T) {
232 tests := map[string]struct {
233 value UnionWithDates
234 expected string
235 }{
236 "date_only": {
237 UnionWithDates{
238 OfDate: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 0, 0, 0, 0, time.UTC)},
239 },
240 `"2023-03-18"`,
241 },
242 "datetime_only": {
243 UnionWithDates{
244 OfTime: param.Opt[time.Time]{Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)},
245 },
246 `"2023-03-18T14:47:38Z"`,
247 },
248 }
249
250 for name, test := range tests {
251 t.Run(name, func(t *testing.T) {
252 b, err := json.Marshal(test.value)
253 if err != nil {
254 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
255 }
256 if string(b) != test.expected {
257 t.Fatalf("expected %s, received %s", test.expected, string(b))
258 }
259 })
260 }
261}
262
263func TestOverride(t *testing.T) {
264 tests := map[string]struct {
265 value param.ParamStruct
266 expected string
267 }{
268 "param_struct": {
269 param.Override[FieldStruct](map[string]any{
270 "a": "hello",
271 "b": 12,
272 "c": nil,
273 }),
274 `{"a":"hello","b":12,"c":null}`,
275 },
276 "param_struct_primitive": {
277 param.Override[FieldStruct](12),
278 `12`,
279 },
280 "param_struct_null": {
281 param.Override[FieldStruct](nil),
282 `null`,
283 },
284 }
285
286 f := FieldStruct{}
287
288 f.SetExtraFields(map[string]any{
289 "z": "ok",
290 })
291
292 for name, test := range tests {
293 t.Run(name, func(t *testing.T) {
294 b, err := json.Marshal(test.value)
295 if err != nil {
296 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
297 }
298 if string(b) != test.expected {
299 t.Fatalf("expected %s, received %s", test.expected, string(b))
300 }
301 if _, ok := test.value.Overrides(); !ok {
302 t.Fatalf("expected to be overridden")
303 }
304 })
305 }
306}
307
308// Despite implementing the interface, this struct is not an param.Optional
309// since it was defined in a different package.
310type almostOpt struct{}
311
312func (almostOpt) Valid() bool { return true }
313func (almostOpt) Null() bool { return false }
314func (almostOpt) isZero() bool { return false }
315
316func (almostOpt) implOpt() {}
317
318func TestOptionalInterfaceAssignability(t *testing.T) {
319 optInt := param.Opt[int]{}
320 if _, ok := any(optInt).(param.Optional); !ok {
321 t.Fatalf("failed to assign")
322 }
323
324 notOpt := almostOpt{}
325 if _, ok := any(notOpt).(param.Optional); ok {
326 t.Fatalf("unexpected successful assignment")
327 }
328
329 notOpt.implOpt() // silence the warning
330}
331
332type PrimitiveUnion struct {
333 OfString param.Opt[string]
334 OfInt param.Opt[int]
335 param.APIUnion
336}
337
338func (p PrimitiveUnion) MarshalJSON() (data []byte, err error) {
339 return param.MarshalUnion(p, p.OfString, p.OfInt)
340}
341
342func TestOverriddenUnion(t *testing.T) {
343 tests := map[string]struct {
344 value PrimitiveUnion
345 expected string
346 }{
347 "string": {
348 param.Override[PrimitiveUnion](json.RawMessage(`"hello"`)),
349 `"hello"`,
350 },
351 "int": {
352 param.Override[PrimitiveUnion](json.RawMessage(`42`)),
353 `42`,
354 },
355 }
356
357 for name, test := range tests {
358 t.Run(name, func(t *testing.T) {
359 b, err := json.Marshal(test.value)
360 if err != nil {
361 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
362 }
363 if string(b) != test.expected {
364 t.Fatalf("expected %s, received %s", test.expected, string(b))
365 }
366 })
367 }
368}
369
370func TestNullStructUnion(t *testing.T) {
371 nullUnion := param.NullStruct[PrimitiveUnion]()
372
373 b, err := json.Marshal(nullUnion)
374 if err != nil {
375 t.Fatalf("didn't expect error %v", err)
376 }
377 if string(b) != "null" {
378 t.Fatalf("expected null, received %s", string(b))
379 }
380}
381
382//
383// Compaction optimization
384//
385
386type NonCompactedDoubleParent struct {
387 Prop string `json:"prop"`
388 Parent NonCompactedParent `json:"parent"`
389
390 param.APIObject
391}
392
393type NonCompactedParent struct {
394 BadChild NonCompacted `json:"bad_child"`
395
396 param.APIObject
397}
398
399type NonCompacted struct {
400 Raw string
401
402 param.APIObject
403}
404
405func (a NonCompactedDoubleParent) MarshalJSON() ([]byte, error) {
406 type shadow NonCompactedDoubleParent
407 return param.MarshalObject(a, (*shadow)(&a))
408}
409
410func (a NonCompactedParent) MarshalJSON() ([]byte, error) {
411 type shadow NonCompactedParent
412 return param.MarshalObject(a, (*shadow)(&a))
413}
414
415func (a NonCompacted) MarshalJSON() ([]byte, error) {
416 if a.Raw == "" {
417 a.Raw = nonCompactedRaw
418 }
419 return []byte(a.Raw), nil
420}
421
422var nonCompactedRaw string = ` { "foo": "bar" } `
423
424func TestAppendCompactBroken(t *testing.T) {
425 tests := map[string]struct {
426 value json.Marshaler
427 }{
428 "red/illegal-json": {
429 NonCompacted{Raw: `{ "broken": "json" `},
430 },
431 "red/nested-with-illegal-json": {
432 NonCompactedParent{BadChild: NonCompacted{
433 Raw: `{ "broken": "json" `,
434 }},
435 },
436 }
437
438 for name, test := range tests {
439 t.Run(name, func(t *testing.T) {
440 v, err := json.Marshal(test.value)
441 if err == nil {
442 t.Fatal("expected error got", v)
443 }
444 })
445 }
446}
447
448// TestAppendCompact validates an optimization for internal SDK types to
449// avoid O(keys^2) iteration over each JSON object.
450//
451// It's possible to intentionally trigger this behavior as both a user and
452// SDK developer. However, the edge case is quite pathological and requires
453// calling [json.Marshaler.MarshalJSON] rather than [json.Marshal].
454func TestAppendCompact(t *testing.T) {
455
456 tests := map[string]struct {
457 value json.Marshaler
458 expected string
459 }{
460 //
461 // Non-compacted cases
462 //
463 // Note this is how to exploit the compacter to fail, you must call [json.Marshaler.MarshalJSON] rather than [json.Marshal].
464 // The type must also embed [param.APIObject] and return non-compacted JSON.
465 //
466
467 "no-compact/fails-compaction": {
468 NonCompacted{Raw: nonCompactedRaw},
469 nonCompactedRaw,
470 },
471 "no-compact/nested-with-bad-child": {
472 NonCompactedParent{BadChild: NonCompacted{
473 Raw: nonCompactedRaw,
474 }},
475 `{"bad_child":` + nonCompactedRaw + `}`,
476 },
477 "no-compact/double-nested-with-bad-child": {
478 NonCompactedDoubleParent{Prop: "1", Parent: NonCompactedParent{BadChild: NonCompacted{
479 Raw: nonCompactedRaw,
480 }}},
481 `{"prop":"1","parent":{"bad_child":` + nonCompactedRaw + `}}`,
482 },
483
484 //
485 // Compacted cases
486 //
487
488 "override/spaces-within": {
489 param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com": "pact"}`)),
490 `{"com":"pact"}`,
491 },
492 "override/spaces-after": {
493 param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com":"pact"} `)),
494 `{"com":"pact"}`,
495 },
496 "override/spaces-before": {
497 param.Override[NonCompactedDoubleParent](json.RawMessage(` {"com":"pact"}`)),
498 `{"com":"pact"}`,
499 },
500 "override/spaces-around": {
501 param.Override[NonCompactedDoubleParent](json.RawMessage(` { "com": "pact" }`)),
502 `{"com":"pact"}`,
503 },
504 "override/override-with-nested": {
505 param.Override[NonCompactedDoubleParent](NonCompactedParent{}),
506 `{"bad_child":{"foo":"bar"}}`,
507 },
508 "override/override-with-non-compacted": {
509 param.Override[NonCompactedDoubleParent](NonCompacted{}),
510 `{"foo":"bar"}`,
511 },
512 }
513
514 for name, test := range tests {
515 t.Run(name+"/marshal-json", func(t *testing.T) {
516 b, err := test.value.MarshalJSON()
517 if err != nil {
518 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
519 }
520 if string(b) != test.expected {
521 t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b))
522 }
523 })
524
525 t.Run(name+"/json-marshal", func(t *testing.T) {
526 b, err := json.Marshal(test.value)
527 if err != nil {
528 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
529 }
530
531 // expected output of JSON Marshal should always be compacted
532 var compactedExpected bytes.Buffer
533 err = json.Compact(&compactedExpected, []byte(test.expected))
534 if err != nil {
535 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
536 }
537
538 if string(b) != compactedExpected.String() {
539 t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b))
540 }
541 })
542
543 t.Run(name+"/shimjson-marshal", func(t *testing.T) {
544 b, err := shimjson.Marshal(test.value)
545 if err != nil {
546 t.Fatalf("didn't expect error %v, expected %s", err, test.expected)
547 }
548 if string(b) != test.expected {
549 t.Logf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b))
550 }
551 })
552 }
553}
554