From 0ddf9735afcde8ebe528d5b8eb8d1e80033d579b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 21:06:55 +0000 Subject: [PATCH] docs: add migration guide (#302) --- MIGRATION.md | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..c269b47 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,250 @@ +# OpenAI Go Migration Guide + +Go Reference + +This SDK includes breaking changes from the previous version to improve the ergonomics of constructing parameters and accessing responses. + +# Request parameters + +## Required primitives parameters serialize their zero values (`string`, `int64`, etc.) + +> [!CAUTION] > **This change can cause new behavior in existing code, without compiler warnings.** + +```diff +type FooParams struct { +- Age param.Field[int64] `json:"age,required"` +- Name param.Field[string] `json:"name"` ++ Age int64 `json:"age,required"` ++ Name param.Opt[string] `json:"name,omitzero"` +} +``` + + + + + + + + + + +
PreviousNew
+ +```go +_ = FooParams{ + Name: openai.String("Jerry") +} +`{"name": "Jerry"}` // (after serialization) +``` + + + +```go +_ = FooParams{ + Name: openai.String("Jerry") +} +`{"name": "Jerry", "age": 0}` // <== Notice the age field +``` + +
+ +The required field `"age"` is now present as `0`. Required primitive fields without the \`json:"...,omitzero"\` struct tag +are always serialized, including their zero values. + +## Transition from `param.Field[T]` to `omitzero` + +The new SDK uses \`json:"...,omitzero"\` semantics from Go 1.24+ for JSON encoding[^1]. + +`omitzero` is used for structs, slices, maps, string enums, and optional primitive types wrapped in `param.Opt[T]` (e.g. `param.Opt[string]`). + +**Fields of a request struct:** + +```diff +type FooParams struct { +- RequiredString param.Field[string] `json:"required_string,required"` ++ RequiredString string `json:"required_string,required"` + +- OptionalString param.Field[string] `json:"optional_string"` ++ OptionalString param.Opt[string] `json:"optional_string,omitzero"` + +- Array param.Field[[]BarParam] `json"array"` ++ Array []BarParam `json"array,omitzero"` + +- Map param.Field[map[string]BarParam] `json"map"` ++ Map map[string]BarParam `json"map,omitzero"` + +- RequiredObject param.Field[BarParam] `json:"required_object,required"` ++ RequiredObject BarParam `json:"required_object,omitzero,required"` + +- OptionalObject param.Field[BarParam] `json:"optional_object"` ++ OptionalObject BarParam `json:"optional_object,omitzero"` + +- StringEnum param.Field[BazEnum] `json:"string_enum"` ++ StringEnum BazEnum `json:"string_enum,omitzero"` +} +``` + +**Previous vs New SDK: Constructing a request** + +```diff +foo = FooParams{ +- RequiredString: openai.String("hello"), ++ RequiredString: "hello", + +- OptionalString: openai.String("hi"), ++ OptionalString: openai.String("hi"), + +- Array: openai.F([]BarParam{ +- BarParam{Prop: ... } +- }), ++ Array: []BarParam{ ++ BarParam{Prop: ... } ++ }, + +- RequiredObject: openai.F(BarParam{ ... }), ++ RequiredObject: BarParam{ ... }, + +- OptionalObject: openai.F(BarParam{ ... }), ++ OptionalObject: BarParam{ ... }, + +- StringEnum: openai.F[BazEnum]("baz-ok"), ++ StringEnum: "baz-ok", +} +``` + +`param.Opt[string]` can be constructed with `openai.String(string)`. Similar functions exist for other primitive +types like `openai.Int(int)`, `openai.Bool(bool)`, etc. + +## Request Unions: Removing interfaces and moving to structs + +For a type `AnimalUnionParam` which could be either a `string | CatParam | DogParam`. + + + + + + + + + + + + + + + + + +
Previous New
+ +```go +type AnimalParam interface { + ImplAnimalParam() +} + +func (Dog) ImplAnimalParam() {} +func (Cat) ImplAnimalParam() {} +``` + + + +```go +type AnimalUnionParam struct { + OfCat *Cat `json:",omitzero,inline` + OfDog *Dog `json:",omitzero,inline` +} +``` + +
+ +```go +var dog AnimalParam = DogParam{ + Name: "spot", ... +} +var cat AnimalParam = CatParam{ + Name: "whiskers", ... +} +``` + + + +```go +dog := AnimalUnionParam{ + OfDog: &DogParam{Name: "spot", ... }, +} +cat := AnimalUnionParam{ + OfCat: &CatParam{Name: "whiskers", ... }, +} +``` + +
+ +```go +var name string +switch v := animal.(type) { +case Dog: + name = v.Name +case Cat: + name = v.Name +} +``` + + + +```go +// Accessing fields +var name *string = animal.GetName() +``` + +
+ +## Sending explicit `null` values + +The old SDK had a function `param.Null[T]()` which could set `param.Field[T]` to `null`. + +The new SDK uses `param.NullOpt[T]()` for to set a `param.Opt[T]` to `null`, +and `param.NullObj[T]()` to set a param struct `T` to `null`. + +```diff +- var nullObj param.Field[BarParam] = param.Null[BarParam]() ++ var nullObj BarParam = param.NullObj[BarParam]() + +- var nullPrimitive param.Field[int64] = param.Null[int64]() ++ var nullPrimitive param.Opt[int64] = param.NullOpt[int64]() +``` + +## Sending custom values + +The `openai.Raw[T](any)` function has been removed. All request structs now support a +`.WithExtraField(map[string]any)` method to customize the fields. + +```diff +foo := FooParams{ + A: param.String("hello"), +- B: param.Raw[string](12) // sending `12` instead of a string +} ++ foo.WithExtraFields(map[string]any{ ++ "B": 12, ++ }) +``` + +# Response Properties + +## Checking for presence of optional fields + +The `.IsNull()` method has been changed to `.IsPresent()` to better reflect its behavior. + +```diff +- if !resp.Foo.JSON.Bar.IsNull() { ++ if resp.Foo.JSON.Bar.IsPresent() { + println("bar is present:", resp.Foo.Bar) + } +``` + +| Previous | New | Returns true for values | +| -------------- | ------------------- | ----------------------- | +| `.IsNull()` | `!.IsPresent()` | `null` or Omitted | +| `.IsMissing()` | `.Raw() == ""` | Omitted | +| `.Invalid()` | `.IsExplicitNull()` | `null` | + +[^1]: The SDK doesn't require Go 1.24, despite supporting the `omitzero` feature