diff --git a/.github/workflows/seed.yml b/.github/workflows/seed.yml index fcbe3cf245a..733869e6c7b 100644 --- a/.github/workflows/seed.yml +++ b/.github/workflows/seed.yml @@ -25,6 +25,7 @@ jobs: postman: ${{ steps.filter.outputs.postman }} java: ${{ steps.filter.outputs.java }} typescript: ${{ steps.filter.outputs.typescript }} + go: ${{ steps.filter.outputs.go }} steps: - uses: actions/checkout@v2 - uses: dorny/paths-filter@v2 @@ -41,6 +42,7 @@ jobs: postman: 'generators/postman/**' java: 'generators/java/**' typescript: 'generators/typescript/**' + go: 'generators/go/**' ruby-model: runs-on: ubuntu-latest @@ -368,3 +370,78 @@ jobs: - name: Ensure no changes to git-tracked files run: git --no-pager diff --exit-code + + go-fiber: + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.go == 'true' || needs.changes.outputs.seed == 'true' }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Yarn Install + run: yarn install + + - name: Seed Test + env: + FORCE_COLOR: "2" + run: | + yarn seed:local test --workspace go-fiber --parallel 16 + + - name: Ensure no changes to git-tracked files + run: git --no-pager diff --exit-code + + go-model: + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.go == 'true' || needs.changes.outputs.seed == 'true' }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Yarn Install + run: yarn install + + - name: Seed Test + env: + FORCE_COLOR: "2" + run: | + yarn seed:local test --workspace go-model --parallel 16 + + - name: Ensure no changes to git-tracked files + run: git --no-pager diff --exit-code + + go-sdk: + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.go == 'true' || needs.changes.outputs.seed == 'true' }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Yarn Install + run: yarn install + + - name: Seed Test + env: + FORCE_COLOR: "2" + run: | + yarn seed:local test --workspace go-sdk --parallel 16 + + - name: Ensure no changes to git-tracked files + run: git --no-pager diff --exit-code diff --git a/generators/go/Makefile b/generators/go/Makefile index 17beea9056c..ce2abb82a77 100644 --- a/generators/go/Makefile +++ b/generators/go/Makefile @@ -16,18 +16,6 @@ install: .PHONY: test test: install go test ./... - npm install -g @fern-api/seed-cli@0.17.0-rc0-2-gde917225c - seed test \ - --fixture bytes \ - --fixture enum \ - --fixture file-upload \ - --fixture idempotency-headers \ - --fixture literal \ - --fixture literal-headers \ - --fixture plain-text \ - --fixture query-parameters \ - --fixture response-property \ - --fixture streaming .PHONY: fixtures fixtures: install diff --git a/generators/go/VERSION b/generators/go/VERSION index 04a373efe6b..2cae6c433a7 100644 --- a/generators/go/VERSION +++ b/generators/go/VERSION @@ -1 +1 @@ -0.16.0 +0.17.0-rc0 diff --git a/generators/go/cmd/fern-go-fiber/main.go b/generators/go/cmd/fern-go-fiber/main.go index b7ebce5c8c4..b099cac78eb 100644 --- a/generators/go/cmd/fern-go-fiber/main.go +++ b/generators/go/cmd/fern-go-fiber/main.go @@ -31,6 +31,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.Organization, config.Version, config.IrFilepath, + config.SnippetFilepath, config.ImportPath, config.PackageName, config.Module, diff --git a/generators/go/cmd/fern-go-model/main.go b/generators/go/cmd/fern-go-model/main.go index 4cbfb8ea1e7..e6f53002856 100644 --- a/generators/go/cmd/fern-go-model/main.go +++ b/generators/go/cmd/fern-go-model/main.go @@ -31,6 +31,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.Organization, config.Version, config.IrFilepath, + config.SnippetFilepath, config.ImportPath, config.PackageName, config.Module, diff --git a/generators/go/cmd/fern-go-sdk/main.go b/generators/go/cmd/fern-go-sdk/main.go index d8dd6391246..de0c67594bc 100644 --- a/generators/go/cmd/fern-go-sdk/main.go +++ b/generators/go/cmd/fern-go-sdk/main.go @@ -31,6 +31,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.Organization, config.Version, config.IrFilepath, + config.SnippetFilepath, config.ImportPath, config.PackageName, config.Module, diff --git a/generators/go/internal/ast/ast.go b/generators/go/internal/ast/ast.go index ce3048cfadf..710a8b0a5c9 100644 --- a/generators/go/internal/ast/ast.go +++ b/generators/go/internal/ast/ast.go @@ -10,6 +10,32 @@ type Expr interface { WriteTo(*Writer) } +// Block represents multiple expressions in a newline-delimited block of code, e.g. +// +// foo := NewFoo() +// value := foo.Bar( +// "one", +// "two", +// ) +type Block struct { + Exprs []Expr +} + +func NewBlock(exprs ...Expr) *Block { + return &Block{ + Exprs: exprs, + } +} + +func (b *Block) isExpr() {} + +func (b *Block) WriteTo(w *Writer) { + for _, expr := range b.Exprs { + w.WriteExpr(expr) + w.WriteLine() + } +} + // AssignStmt is an assignment or a short variable declaration, e.g. // // value := foo.Bar( @@ -21,16 +47,16 @@ type AssignStmt struct { Right []Expr } -func NewAssignStmt(left, right []Expr) AssignStmt { - return AssignStmt{ +func NewAssignStmt(left, right []Expr) *AssignStmt { + return &AssignStmt{ Left: left, Right: right, } } -func (a AssignStmt) isExpr() {} +func (a *AssignStmt) isExpr() {} -func (a AssignStmt) WriteTo(w *Writer) { +func (a *AssignStmt) WriteTo(w *Writer) { for i, elem := range a.Left { if i > 0 { w.Write(", ") @@ -53,36 +79,26 @@ func (a AssignStmt) WriteTo(w *Writer) { // "two", // ) type CallExpr struct { - FunctionName Object + FunctionName Reference Parameters []Expr } -func NewCallExpr(functionName Object, parameters []Expr) CallExpr { - return CallExpr{ +func NewCallExpr(functionName Reference, parameters []Expr) *CallExpr { + return &CallExpr{ FunctionName: functionName, Parameters: parameters, } } -func (c CallExpr) isExpr() {} +func (c *CallExpr) isExpr() {} -func (c CallExpr) WriteTo(w *Writer) { +func (c *CallExpr) WriteTo(w *Writer) { w.WriteExpr(c.FunctionName) if len(c.Parameters) == 0 { w.Write("()") return } - if len(c.Parameters) == 1 { - // A single parameter can be written in one line, e.g. acme.Call("one") - w.Write("(") - for _, param := range c.Parameters { - w.WriteExpr(param) - } - w.Write(")") - return - } - w.WriteLine("(") for _, param := range c.Parameters { w.WriteExpr(param) @@ -91,56 +107,225 @@ func (c CallExpr) WriteTo(w *Writer) { w.Write(")") } -// Object is either a local or imported object, such as a constant, +// Reference is either a local or imported reference, such as a constant, // type, variable, function, etc. -type Object interface { +type Reference interface { Expr - isObject() + isReference() +} + +// MapType is a simple map type AST node, which does not include the +// map's values. +type MapType struct { + Key Expr + Value Expr +} + +func NewMapType(key Reference, value Reference) *MapType { + return &MapType{ + Key: key, + Value: value, + } +} + +func (m *MapType) isExpr() {} + +func (m *MapType) WriteTo(w *Writer) { + w.Write("map[") + w.WriteExpr(m.Key) + w.Write("]") + w.WriteExpr(m.Value) +} + +// ArrayType is a simple array type AST node, which does not include the +// array's values. +type ArrayType struct { + Expr Expr +} + +func NewArrayType(expr Expr) *ArrayType { + return &ArrayType{ + Expr: expr, + } +} + +func (a *ArrayType) isExpr() {} + +func (a *ArrayType) WriteTo(w *Writer) { + w.Write("[]") + w.WriteExpr(a.Expr) } -// ImportedObject is a named language entity imported from another package, +// ImportedReference is a named language entity imported from another package, // such as a constant, type, variable, function, etc. // // Unlike the go/ast package, this also includes literal // values (e.g. "foo"). -type ImportedObject struct { +type ImportedReference struct { Name string ImportPath string } -func NewImportedObject(name string, importPath string) ImportedObject { - return ImportedObject{ +func NewImportedReference(name string, importPath string) *ImportedReference { + return &ImportedReference{ Name: name, ImportPath: importPath, } } -func (i ImportedObject) isExpr() {} -func (i ImportedObject) isObject() {} +func (i *ImportedReference) isExpr() {} +func (i *ImportedReference) isReference() {} -func (i ImportedObject) WriteTo(w *Writer) { +func (i *ImportedReference) WriteTo(w *Writer) { alias := w.scope.AddImport(i.ImportPath) w.Write(fmt.Sprintf("%s.%s", alias, i.Name)) } -// LocalObject is a named language entity referenced within the same +// LocalReference is a named language entity referenced within the same // package such as a constant, type, variable, function, etc. -// -// Unlike the go/ast package, this also includes literal values (e.g. "foo"). -type LocalObject struct { +type LocalReference struct { Name string } -func NewLocalObject(name string) LocalObject { - return LocalObject{ +func NewLocalReference(name string) *LocalReference { + return &LocalReference{ Name: name, } } -func (l LocalObject) isExpr() {} -func (l LocalObject) isObject() {} +func (l *LocalReference) isExpr() {} +func (l *LocalReference) isReference() {} -func (l LocalObject) WriteTo(w *Writer) { +func (l LocalReference) WriteTo(w *Writer) { w.Write(l.Name) } + +// Optional is an optional type, such that it needs a pointer in its declaration. +type Optional struct { + Expr Expr +} + +func NewOptional(expr Expr) *Optional { + return &Optional{ + Expr: expr, + } +} + +func (o *Optional) isExpr() {} + +func (o *Optional) WriteTo(w *Writer) { + w.Write("*") + w.WriteExpr(o.Expr) +} + +// Field is an individual struct field. +type Field struct { + Key string + Value Expr +} + +func NewField(key string, value Expr) Field { + return Field{ + Key: key, + Value: value, + } +} + +func (f *Field) isExpr() {} + +func (f *Field) WriteTo(w *Writer) { + w.Write(f.Key) + w.Write(": ") + w.WriteExpr(f.Value) +} + +// StructType is an individual struct instance. +type StructType struct { + Name Reference + Fields []*Field +} + +func NewStructType(name Reference, fields []*Field) *StructType { + return &StructType{ + Name: name, + Fields: fields, + } +} + +func (s *StructType) isExpr() {} + +func (s *StructType) WriteTo(w *Writer) { + w.Write("&") + w.WriteExpr(s.Name) + w.WriteLine("{") + for _, field := range s.Fields { + w.WriteExpr(field) + w.WriteLine(",") + } + w.Write("}") +} + +type ArrayLit struct { + Type *ArrayType + Values []Expr +} + +func (a *ArrayLit) isExpr() {} + +func (a *ArrayLit) WriteTo(w *Writer) { + if len(a.Values) == 0 { + w.Write("nil") + return + } + + w.WriteExpr(a.Type) + w.WriteLine("{") + for _, value := range a.Values { + w.WriteExpr(value) + w.WriteLine(",") + } + w.Write("}") +} + +type MapLit struct { + Type *MapType + Keys []Expr + Values []Expr +} + +func (m *MapLit) isExpr() {} + +func (m *MapLit) WriteTo(w *Writer) { + if len(m.Keys) == 0 { + w.Write("nil") + return + } + w.WriteExpr(m.Type) + w.WriteLine("{") + for i, key := range m.Keys { + w.WriteExpr(key) + w.Write(": ") + w.WriteExpr(m.Values[i]) + w.WriteLine(",") + } + w.Write("}") +} + +// BasicLit is a basic literal represented as a string value +// (e.g. 42, "foo", false, etc). +type BasicLit struct { + Value string +} + +func NewBasicLit(value string) *BasicLit { + return &BasicLit{ + Value: value, + } +} + +func (b *BasicLit) isExpr() {} + +func (b *BasicLit) WriteTo(w *Writer) { + w.Write(b.Value) +} diff --git a/generators/go/internal/ast/ast_test.go b/generators/go/internal/ast/ast_test.go index ff4ab5bd392..f0d06a0e87e 100644 --- a/generators/go/internal/ast/ast_test.go +++ b/generators/go/internal/ast/ast_test.go @@ -10,24 +10,24 @@ import ( func TestSourceCodeBuilder(t *testing.T) { builder := NewSourceCodeBuilder() builder.AddExpr( - AssignStmt{ + &AssignStmt{ Left: []Expr{ - NewLocalObject("value"), + NewLocalReference("value"), }, Right: []Expr{ NewCallExpr( - NewImportedObject( + NewImportedReference( "foo", "example.io/bar", ), []Expr{ - NewLocalObject(`"one"`), - NewLocalObject(`"two"`), - NewImportedObject( + NewBasicLit(`"one"`), + NewBasicLit(`"two"`), + NewImportedReference( "Value", "example.io/enum", ), - NewImportedObject( + NewImportedReference( "Collision", "example.io/another/enum", ), diff --git a/generators/go/internal/ast/doc.go b/generators/go/internal/ast/doc.go index 2d8ad94569f..65b1eb4edf2 100644 --- a/generators/go/internal/ast/doc.go +++ b/generators/go/internal/ast/doc.go @@ -1,3 +1,6 @@ // Package ast defines AST types suitable for code generation, // akin to other libraries like JavaPoet. +// +// The type names strive to be consistent with those found in +// the standard go/ast library. package ast diff --git a/generators/go/internal/cmd/cmd.go b/generators/go/internal/cmd/cmd.go index c8f92b04f37..8a1e211d9e7 100644 --- a/generators/go/internal/cmd/cmd.go +++ b/generators/go/internal/cmd/cmd.go @@ -60,6 +60,7 @@ type Config struct { CoordinatorTaskID string Version string IrFilepath string + SnippetFilepath string ImportPath string PackageName string Module *generator.ModuleConfig @@ -188,6 +189,12 @@ func newConfig(configFilename string) (*Config, error) { coordinatorURL = config.Environment.Remote.CoordinatorUrlV2 coordinatorTaskID = config.Environment.Remote.Id } + + var snippetFilepath string + if config.Output != nil && config.Output.SnippetFilepath != nil { + snippetFilepath = *config.Output.SnippetFilepath + } + return &Config{ DryRun: config.DryRun, IncludeLegacyClientOptions: customConfig.IncludeLegacyClientOptions, @@ -198,6 +205,7 @@ func newConfig(configFilename string) (*Config, error) { CoordinatorTaskID: coordinatorTaskID, Version: outputVersionFromGeneratorConfig(config), IrFilepath: config.IrFilepath, + SnippetFilepath: snippetFilepath, ImportPath: customConfig.ImportPath, PackageName: customConfig.PackageName, Module: moduleConfig, diff --git a/generators/go/internal/fern/ir.json b/generators/go/internal/fern/ir.json index bd19b99c089..68ab7b82f7a 100644 --- a/generators/go/internal/fern/ir.json +++ b/generators/go/internal/fern/ir.json @@ -19,7 +19,7 @@ } }, "apiDisplayName": null, - "apiDocs": "Adds support for undiscriminated union examples", + "apiDocs": "Adds support for simple object query parameters.", "auth": { "requirement": "ALL", "schemes": [], @@ -4329,6 +4329,88 @@ "availability": null, "docs": null }, + "type_commons:WebsocketChannelId": { + "name": { + "name": { + "originalName": "WebsocketChannelId", + "camelCase": { + "unsafeName": "websocketChannelId", + "safeName": "websocketChannelId" + }, + "snakeCase": { + "unsafeName": "websocket_channel_id", + "safeName": "websocket_channel_id" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_CHANNEL_ID", + "safeName": "WEBSOCKET_CHANNEL_ID" + }, + "pascalCase": { + "unsafeName": "WebsocketChannelId", + "safeName": "WebsocketChannelId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WebsocketChannelId" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": "STRING" + }, + "resolvedType": { + "_type": "primitive", + "primitive": "STRING" + } + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, "type_commons:Declaration": { "name": { "name": { @@ -9422,7 +9504,11 @@ "type_http:ExampleInlinedRequestBodyProperty", "type_http:ExampleResponse", "type_http:ExampleEndpointSuccessResponse", - "type_http:ExampleEndpointErrorResponse" + "type_http:ExampleEndpointErrorResponse", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage" ], "examples": [], "availability": null, @@ -11348,7 +11434,11 @@ "type_http:ExampleInlinedRequestBodyProperty", "type_http:ExampleResponse", "type_http:ExampleEndpointSuccessResponse", - "type_http:ExampleEndpointErrorResponse" + "type_http:ExampleEndpointErrorResponse", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage" ], "examples": [], "availability": null, @@ -20873,6 +20963,113 @@ }, "availability": null, "docs": null + }, + { + "name": { + "name": { + "originalName": "codeSamples", + "camelCase": { + "unsafeName": "codeSamples", + "safeName": "codeSamples" + }, + "snakeCase": { + "unsafeName": "code_samples", + "safeName": "code_samples" + }, + "screamingSnakeCase": { + "unsafeName": "CODE_SAMPLES", + "safeName": "CODE_SAMPLES" + }, + "pascalCase": { + "unsafeName": "CodeSamples", + "safeName": "CodeSamples" + } + }, + "wireValue": "codeSamples" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "ExampleCodeSample", + "camelCase": { + "unsafeName": "exampleCodeSample", + "safeName": "exampleCodeSample" + }, + "snakeCase": { + "unsafeName": "example_code_sample", + "safeName": "example_code_sample" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_CODE_SAMPLE", + "safeName": "EXAMPLE_CODE_SAMPLE" + }, + "pascalCase": { + "unsafeName": "ExampleCodeSample", + "safeName": "ExampleCodeSample" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:ExampleCodeSample" + } + } + } + } + }, + "availability": { + "status": "IN_DEVELOPMENT", + "message": null + }, + "docs": "Hand-written code samples for this endpoint. These code samples should match the\nexample that it's attached to, so that we can spin up an API Playground with\nthe code sample that's being displayed in the API Reference." } ] }, @@ -20912,31 +21109,35 @@ "type_http:ExampleEndpointSuccessResponse", "type_http:ExampleEndpointErrorResponse", "type_errors:DeclaredErrorName", - "type_commons:ErrorId" + "type_commons:ErrorId", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage" ], "examples": [], "availability": null, "docs": null }, - "type_http:ExamplePathParameter": { + "type_http:ExampleCodeSample": { "name": { "name": { - "originalName": "ExamplePathParameter", + "originalName": "ExampleCodeSample", "camelCase": { - "unsafeName": "examplePathParameter", - "safeName": "examplePathParameter" + "unsafeName": "exampleCodeSample", + "safeName": "exampleCodeSample" }, "snakeCase": { - "unsafeName": "example_path_parameter", - "safeName": "example_path_parameter" + "unsafeName": "example_code_sample", + "safeName": "example_code_sample" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_PATH_PARAMETER", - "safeName": "EXAMPLE_PATH_PARAMETER" + "unsafeName": "EXAMPLE_CODE_SAMPLE", + "safeName": "EXAMPLE_CODE_SAMPLE" }, "pascalCase": { - "unsafeName": "ExamplePathParameter", - "safeName": "ExamplePathParameter" + "unsafeName": "ExampleCodeSample", + "safeName": "ExampleCodeSample" } }, "fernFilepath": { @@ -20982,247 +21183,253 @@ } } }, - "typeId": "type_http:ExamplePathParameter" + "typeId": "type_http:ExampleCodeSample" }, "shape": { - "_type": "object", + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, "extends": [], - "properties": [ + "baseProperties": [], + "types": [ { - "name": { + "discriminantValue": { "name": { - "originalName": "name", + "originalName": "language", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "language", + "safeName": "language" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "language", + "safeName": "language" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "LANGUAGE", + "safeName": "LANGUAGE" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Language", + "safeName": "Language" } }, - "wireValue": "name" + "wireValue": "language" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "Name", + "originalName": "ExampleCodeSampleLanguage", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "exampleCodeSampleLanguage", + "safeName": "exampleCodeSampleLanguage" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "example_code_sample_language", + "safeName": "example_code_sample_language" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "EXAMPLE_CODE_SAMPLE_LANGUAGE", + "safeName": "EXAMPLE_CODE_SAMPLE_LANGUAGE" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "ExampleCodeSampleLanguage", + "safeName": "ExampleCodeSampleLanguage" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_commons:Name" + "typeId": "type_http:ExampleCodeSampleLanguage" }, - "availability": null, "docs": null }, { - "name": { + "discriminantValue": { "name": { - "originalName": "value", + "originalName": "sdk", "camelCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "sdk", + "safeName": "sdk" }, "snakeCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "sdk", + "safeName": "sdk" }, "screamingSnakeCase": { - "unsafeName": "VALUE", - "safeName": "VALUE" + "unsafeName": "SDK", + "safeName": "SDK" }, "pascalCase": { - "unsafeName": "Value", - "safeName": "Value" + "unsafeName": "Sdk", + "safeName": "Sdk" } }, - "wireValue": "value" + "wireValue": "sdk" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "ExampleTypeReference", + "originalName": "ExampleCodeSampleSdk", "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" + "unsafeName": "exampleCodeSampleSdk", + "safeName": "exampleCodeSampleSdk" }, "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" + "unsafeName": "example_code_sample_sdk", + "safeName": "example_code_sample_sdk" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" + "unsafeName": "EXAMPLE_CODE_SAMPLE_SDK", + "safeName": "EXAMPLE_CODE_SAMPLE_SDK" }, "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" + "unsafeName": "ExampleCodeSampleSdk", + "safeName": "ExampleCodeSampleSdk" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "http", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "http", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_types:ExampleTypeReference" + "typeId": "type_http:ExampleCodeSampleSdk" }, - "availability": null, "docs": null } ] }, "referencedTypes": [ + "type_http:ExampleCodeSampleLanguage", + "type_commons:WithDocs", "type_commons:Name", "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage" ], "examples": [], - "availability": null, + "availability": { + "status": "IN_DEVELOPMENT", + "message": null + }, "docs": null }, - "type_http:ExampleQueryParameter": { + "type_http:ExampleCodeSampleLanguage": { "name": { "name": { - "originalName": "ExampleQueryParameter", + "originalName": "ExampleCodeSampleLanguage", "camelCase": { - "unsafeName": "exampleQueryParameter", - "safeName": "exampleQueryParameter" + "unsafeName": "exampleCodeSampleLanguage", + "safeName": "exampleCodeSampleLanguage" }, "snakeCase": { - "unsafeName": "example_query_parameter", - "safeName": "example_query_parameter" + "unsafeName": "example_code_sample_language", + "safeName": "example_code_sample_language" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_QUERY_PARAMETER", - "safeName": "EXAMPLE_QUERY_PARAMETER" + "unsafeName": "EXAMPLE_CODE_SAMPLE_LANGUAGE", + "safeName": "EXAMPLE_CODE_SAMPLE_LANGUAGE" }, "pascalCase": { - "unsafeName": "ExampleQueryParameter", - "safeName": "ExampleQueryParameter" + "unsafeName": "ExampleCodeSampleLanguage", + "safeName": "ExampleCodeSampleLanguage" } }, "fernFilepath": { @@ -21268,11 +21475,77 @@ } } }, - "typeId": "type_http:ExampleQueryParameter" + "typeId": "type_http:ExampleCodeSampleLanguage" }, "shape": { "_type": "object", - "extends": [], + "extends": [ + { + "name": { + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" + }, + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithDocs" + } + ], "properties": [ { "name": { @@ -21298,70 +21571,106 @@ "wireValue": "name" }, "valueType": { - "_type": "named", - "name": { - "originalName": "NameAndWireValue", - "camelCase": { - "unsafeName": "nameAndWireValue", - "safeName": "nameAndWireValue" - }, - "snakeCase": { - "unsafeName": "name_and_wire_value", - "safeName": "name_and_wire_value" - }, - "screamingSnakeCase": { - "unsafeName": "NAME_AND_WIRE_VALUE", - "safeName": "NAME_AND_WIRE_VALUE" - }, - "pascalCase": { - "unsafeName": "NameAndWireValue", - "safeName": "NameAndWireValue" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "Name", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Name", + "safeName": "Name" } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } + "typeId": "type_commons:Name" + } + } + }, + "availability": null, + "docs": "Override the example name." + }, + { + "name": { + "name": { + "originalName": "language", + "camelCase": { + "unsafeName": "language", + "safeName": "language" + }, + "snakeCase": { + "unsafeName": "language", + "safeName": "language" + }, + "screamingSnakeCase": { + "unsafeName": "LANGUAGE", + "safeName": "LANGUAGE" + }, + "pascalCase": { + "unsafeName": "Language", + "safeName": "Language" } }, - "typeId": "type_commons:NameAndWireValue" + "wireValue": "language" + }, + "valueType": { + "_type": "primitive", + "primitive": "STRING" }, "availability": null, "docs": null @@ -21369,146 +21678,99 @@ { "name": { "name": { - "originalName": "value", + "originalName": "code", "camelCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "code", + "safeName": "code" }, "snakeCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "code", + "safeName": "code" }, "screamingSnakeCase": { - "unsafeName": "VALUE", - "safeName": "VALUE" + "unsafeName": "CODE", + "safeName": "CODE" }, "pascalCase": { - "unsafeName": "Value", - "safeName": "Value" + "unsafeName": "Code", + "safeName": "Code" } }, - "wireValue": "value" + "wireValue": "code" }, "valueType": { - "_type": "named", + "_type": "primitive", + "primitive": "STRING" + }, + "availability": null, + "docs": null + }, + { + "name": { "name": { - "originalName": "ExampleTypeReference", + "originalName": "install", "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" + "unsafeName": "install", + "safeName": "install" }, "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" + "unsafeName": "install", + "safeName": "install" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" + "unsafeName": "INSTALL", + "safeName": "INSTALL" }, "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" + "unsafeName": "Install", + "safeName": "Install" } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "wireValue": "install" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": "STRING" } - }, - "typeId": "type_types:ExampleTypeReference" + } }, "availability": null, - "docs": null + "docs": "The command to install the dependencies for the code sample.\nFor example, `npm install` or `pip install -r requirements.txt`." } ] }, "referencedTypes": [ - "type_commons:NameAndWireValue", + "type_commons:WithDocs", "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_commons:SafeAndUnsafeString" ], "examples": [], "availability": null, - "docs": null + "docs": "This is intended to co-exist with the auto-generated code samples." }, - "type_http:ExampleHeader": { + "type_http:ExampleCodeSampleSdk": { "name": { "name": { - "originalName": "ExampleHeader", + "originalName": "ExampleCodeSampleSdk", "camelCase": { - "unsafeName": "exampleHeader", - "safeName": "exampleHeader" + "unsafeName": "exampleCodeSampleSdk", + "safeName": "exampleCodeSampleSdk" }, "snakeCase": { - "unsafeName": "example_header", - "safeName": "example_header" + "unsafeName": "example_code_sample_sdk", + "safeName": "example_code_sample_sdk" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_HEADER", - "safeName": "EXAMPLE_HEADER" + "unsafeName": "EXAMPLE_CODE_SAMPLE_SDK", + "safeName": "EXAMPLE_CODE_SAMPLE_SDK" }, "pascalCase": { - "unsafeName": "ExampleHeader", - "safeName": "ExampleHeader" + "unsafeName": "ExampleCodeSampleSdk", + "safeName": "ExampleCodeSampleSdk" } }, "fernFilepath": { @@ -21554,11 +21816,77 @@ } } }, - "typeId": "type_http:ExampleHeader" + "typeId": "type_http:ExampleCodeSampleSdk" }, "shape": { "_type": "object", - "extends": [], + "extends": [ + { + "name": { + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" + }, + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithDocs" + } + ], "properties": [ { "name": { @@ -21583,71 +21911,169 @@ }, "wireValue": "name" }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Name" + } + } + }, + "availability": null, + "docs": "Override the example name." + }, + { + "name": { + "name": { + "originalName": "sdk", + "camelCase": { + "unsafeName": "sdk", + "safeName": "sdk" + }, + "snakeCase": { + "unsafeName": "sdk", + "safeName": "sdk" + }, + "screamingSnakeCase": { + "unsafeName": "SDK", + "safeName": "SDK" + }, + "pascalCase": { + "unsafeName": "Sdk", + "safeName": "Sdk" + } + }, + "wireValue": "sdk" + }, "valueType": { "_type": "named", "name": { - "originalName": "NameAndWireValue", + "originalName": "SupportedSdkLanguage", "camelCase": { - "unsafeName": "nameAndWireValue", - "safeName": "nameAndWireValue" + "unsafeName": "supportedSdkLanguage", + "safeName": "supportedSdkLanguage" }, "snakeCase": { - "unsafeName": "name_and_wire_value", - "safeName": "name_and_wire_value" + "unsafeName": "supported_sdk_language", + "safeName": "supported_sdk_language" }, "screamingSnakeCase": { - "unsafeName": "NAME_AND_WIRE_VALUE", - "safeName": "NAME_AND_WIRE_VALUE" + "unsafeName": "SUPPORTED_SDK_LANGUAGE", + "safeName": "SUPPORTED_SDK_LANGUAGE" }, "pascalCase": { - "unsafeName": "NameAndWireValue", - "safeName": "NameAndWireValue" + "unsafeName": "SupportedSdkLanguage", + "safeName": "SupportedSdkLanguage" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_commons:NameAndWireValue" + "typeId": "type_http:SupportedSdkLanguage" }, "availability": null, "docs": null @@ -21655,91 +22081,29 @@ { "name": { "name": { - "originalName": "value", + "originalName": "code", "camelCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "code", + "safeName": "code" }, "snakeCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "code", + "safeName": "code" }, "screamingSnakeCase": { - "unsafeName": "VALUE", - "safeName": "VALUE" + "unsafeName": "CODE", + "safeName": "CODE" }, "pascalCase": { - "unsafeName": "Value", - "safeName": "Value" + "unsafeName": "Code", + "safeName": "Code" } }, - "wireValue": "value" + "wireValue": "code" }, "valueType": { - "_type": "named", - "name": { - "originalName": "ExampleTypeReference", - "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" - }, - "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" - }, - "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" - }, - "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:ExampleTypeReference" + "_type": "primitive", + "primitive": "STRING" }, "availability": null, "docs": null @@ -21747,54 +22111,34 @@ ] }, "referencedTypes": [ - "type_commons:NameAndWireValue", + "type_commons:WithDocs", "type_commons:Name", "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_http:SupportedSdkLanguage" ], "examples": [], "availability": null, - "docs": null + "docs": "This will be used to replace the auto-generated code samples." }, - "type_http:ExampleRequestBody": { + "type_http:SupportedSdkLanguage": { "name": { "name": { - "originalName": "ExampleRequestBody", + "originalName": "SupportedSdkLanguage", "camelCase": { - "unsafeName": "exampleRequestBody", - "safeName": "exampleRequestBody" + "unsafeName": "supportedSdkLanguage", + "safeName": "supportedSdkLanguage" }, "snakeCase": { - "unsafeName": "example_request_body", - "safeName": "example_request_body" + "unsafeName": "supported_sdk_language", + "safeName": "supported_sdk_language" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_REQUEST_BODY", - "safeName": "EXAMPLE_REQUEST_BODY" + "unsafeName": "SUPPORTED_SDK_LANGUAGE", + "safeName": "SUPPORTED_SDK_LANGUAGE" }, "pascalCase": { - "unsafeName": "ExampleRequestBody", - "safeName": "ExampleRequestBody" + "unsafeName": "SupportedSdkLanguage", + "safeName": "SupportedSdkLanguage" } }, "fernFilepath": { @@ -21840,151 +22184,413 @@ } } }, - "typeId": "type_http:ExampleRequestBody" + "typeId": "type_http:SupportedSdkLanguage" }, "shape": { - "_type": "union", - "discriminant": { - "name": { - "originalName": "type", + "_type": "enum", + "values": [ + { + "name": { + "name": { + "originalName": "curl", + "camelCase": { + "unsafeName": "curl", + "safeName": "curl" + }, + "snakeCase": { + "unsafeName": "curl", + "safeName": "curl" + }, + "screamingSnakeCase": { + "unsafeName": "CURL", + "safeName": "CURL" + }, + "pascalCase": { + "unsafeName": "Curl", + "safeName": "Curl" + } + }, + "wireValue": "curl" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "python", + "camelCase": { + "unsafeName": "python", + "safeName": "python" + }, + "snakeCase": { + "unsafeName": "python", + "safeName": "python" + }, + "screamingSnakeCase": { + "unsafeName": "PYTHON", + "safeName": "PYTHON" + }, + "pascalCase": { + "unsafeName": "Python", + "safeName": "Python" + } + }, + "wireValue": "python" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "javascript", + "camelCase": { + "unsafeName": "javascript", + "safeName": "javascript" + }, + "snakeCase": { + "unsafeName": "javascript", + "safeName": "javascript" + }, + "screamingSnakeCase": { + "unsafeName": "JAVASCRIPT", + "safeName": "JAVASCRIPT" + }, + "pascalCase": { + "unsafeName": "Javascript", + "safeName": "Javascript" + } + }, + "wireValue": "javascript" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "typescript", + "camelCase": { + "unsafeName": "typescript", + "safeName": "typescript" + }, + "snakeCase": { + "unsafeName": "typescript", + "safeName": "typescript" + }, + "screamingSnakeCase": { + "unsafeName": "TYPESCRIPT", + "safeName": "TYPESCRIPT" + }, + "pascalCase": { + "unsafeName": "Typescript", + "safeName": "Typescript" + } + }, + "wireValue": "typescript" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "go", + "camelCase": { + "unsafeName": "go", + "safeName": "go" + }, + "snakeCase": { + "unsafeName": "go", + "safeName": "go" + }, + "screamingSnakeCase": { + "unsafeName": "GO", + "safeName": "GO" + }, + "pascalCase": { + "unsafeName": "Go", + "safeName": "Go" + } + }, + "wireValue": "go" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "ruby", + "camelCase": { + "unsafeName": "ruby", + "safeName": "ruby" + }, + "snakeCase": { + "unsafeName": "ruby", + "safeName": "ruby" + }, + "screamingSnakeCase": { + "unsafeName": "RUBY", + "safeName": "RUBY" + }, + "pascalCase": { + "unsafeName": "Ruby", + "safeName": "Ruby" + } + }, + "wireValue": "ruby" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "csharp", + "camelCase": { + "unsafeName": "csharp", + "safeName": "csharp" + }, + "snakeCase": { + "unsafeName": "csharp", + "safeName": "csharp" + }, + "screamingSnakeCase": { + "unsafeName": "CSHARP", + "safeName": "CSHARP" + }, + "pascalCase": { + "unsafeName": "Csharp", + "safeName": "Csharp" + } + }, + "wireValue": "csharp" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "java", + "camelCase": { + "unsafeName": "java", + "safeName": "java" + }, + "snakeCase": { + "unsafeName": "java", + "safeName": "java" + }, + "screamingSnakeCase": { + "unsafeName": "JAVA", + "safeName": "JAVA" + }, + "pascalCase": { + "unsafeName": "Java", + "safeName": "Java" + } + }, + "wireValue": "java" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, + "type_http:ExamplePathParameter": { + "name": { + "name": { + "originalName": "ExamplePathParameter", + "camelCase": { + "unsafeName": "examplePathParameter", + "safeName": "examplePathParameter" + }, + "snakeCase": { + "unsafeName": "example_path_parameter", + "safeName": "example_path_parameter" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_PATH_PARAMETER", + "safeName": "EXAMPLE_PATH_PARAMETER" + }, + "pascalCase": { + "unsafeName": "ExamplePathParameter", + "safeName": "ExamplePathParameter" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", "camelCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" + "unsafeName": "Http", + "safeName": "Http" } - }, - "wireValue": "type" + } }, + "typeId": "type_http:ExamplePathParameter" + }, + "shape": { + "_type": "object", "extends": [], - "baseProperties": [], - "types": [ + "properties": [ { - "discriminantValue": { + "name": { "name": { - "originalName": "inlinedRequestBody", + "originalName": "name", "camelCase": { - "unsafeName": "inlinedRequestBody", - "safeName": "inlinedRequestBody" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "inlined_request_body", - "safeName": "inlined_request_body" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "INLINED_REQUEST_BODY", - "safeName": "INLINED_REQUEST_BODY" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "InlinedRequestBody", - "safeName": "InlinedRequestBody" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "inlinedRequestBody" + "wireValue": "name" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "ExampleInlinedRequestBody", + "originalName": "Name", "camelCase": { - "unsafeName": "exampleInlinedRequestBody", - "safeName": "exampleInlinedRequestBody" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "example_inlined_request_body", - "safeName": "example_inlined_request_body" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY", - "safeName": "EXAMPLE_INLINED_REQUEST_BODY" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "ExampleInlinedRequestBody", - "safeName": "ExampleInlinedRequestBody" + "unsafeName": "Name", + "safeName": "Name" } }, "fernFilepath": { "allParts": [ { - "originalName": "http", + "originalName": "commons", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "http", + "originalName": "commons", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_http:ExampleInlinedRequestBody" + "typeId": "type_commons:Name" }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "reference", + "originalName": "value", "camelCase": { - "unsafeName": "reference", - "safeName": "reference" + "unsafeName": "value", + "safeName": "value" }, "snakeCase": { - "unsafeName": "reference", - "safeName": "reference" + "unsafeName": "value", + "safeName": "value" }, "screamingSnakeCase": { - "unsafeName": "REFERENCE", - "safeName": "REFERENCE" + "unsafeName": "VALUE", + "safeName": "VALUE" }, "pascalCase": { - "unsafeName": "Reference", - "safeName": "Reference" + "unsafeName": "Value", + "safeName": "Value" } }, - "wireValue": "reference" + "wireValue": "value" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { "originalName": "ExampleTypeReference", "camelCase": { @@ -22049,18 +22655,16 @@ }, "typeId": "type_types:ExampleTypeReference" }, + "availability": null, "docs": null } ] }, "referencedTypes": [ - "type_http:ExampleInlinedRequestBody", - "type_commons:WithJsonExample", - "type_http:ExampleInlinedRequestBodyProperty", - "type_commons:NameAndWireValue", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", "type_types:ExampleTypeReferenceShape", "type_types:ExamplePrimitive", "type_commons:EscapedString", @@ -22073,6 +22677,7 @@ "type_types:ExampleTypeShape", "type_types:ExampleAliasType", "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", "type_types:ExampleObjectType", "type_types:ExampleObjectProperty", "type_types:ExampleUnionType", @@ -22085,25 +22690,25 @@ "availability": null, "docs": null }, - "type_http:ExampleInlinedRequestBody": { + "type_http:ExampleQueryParameter": { "name": { "name": { - "originalName": "ExampleInlinedRequestBody", + "originalName": "ExampleQueryParameter", "camelCase": { - "unsafeName": "exampleInlinedRequestBody", - "safeName": "exampleInlinedRequestBody" + "unsafeName": "exampleQueryParameter", + "safeName": "exampleQueryParameter" }, "snakeCase": { - "unsafeName": "example_inlined_request_body", - "safeName": "example_inlined_request_body" + "unsafeName": "example_query_parameter", + "safeName": "example_query_parameter" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY", - "safeName": "EXAMPLE_INLINED_REQUEST_BODY" + "unsafeName": "EXAMPLE_QUERY_PARAMETER", + "safeName": "EXAMPLE_QUERY_PARAMETER" }, "pascalCase": { - "unsafeName": "ExampleInlinedRequestBody", - "safeName": "ExampleInlinedRequestBody" + "unsafeName": "ExampleQueryParameter", + "safeName": "ExampleQueryParameter" } }, "fernFilepath": { @@ -22149,34 +22754,80 @@ } } }, - "typeId": "type_http:ExampleInlinedRequestBody" + "typeId": "type_http:ExampleQueryParameter" }, "shape": { "_type": "object", - "extends": [ + "extends": [], + "properties": [ { "name": { - "originalName": "WithJsonExample", - "camelCase": { - "unsafeName": "withJsonExample", - "safeName": "withJsonExample" - }, - "snakeCase": { - "unsafeName": "with_json_example", - "safeName": "with_json_example" - }, - "screamingSnakeCase": { - "unsafeName": "WITH_JSON_EXAMPLE", - "safeName": "WITH_JSON_EXAMPLE" + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } }, - "pascalCase": { - "unsafeName": "WithJsonExample", - "safeName": "WithJsonExample" - } + "wireValue": "name" }, - "fernFilepath": { - "allParts": [ - { + "valueType": { + "_type": "named", + "name": { + "originalName": "NameAndWireValue", + "camelCase": { + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" + }, + "snakeCase": { + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" + }, + "screamingSnakeCase": { + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" + }, + "pascalCase": { + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { "originalName": "commons", "camelCase": { "unsafeName": "commons", @@ -22195,126 +22846,100 @@ "safeName": "Commons" } } - ], - "packagePath": [], - "file": { - "originalName": "commons", + }, + "typeId": "type_commons:NameAndWireValue" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "value", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "value", + "safeName": "value" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "value", + "safeName": "value" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "VALUE", + "safeName": "VALUE" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Value", + "safeName": "Value" } - } + }, + "wireValue": "value" }, - "typeId": "type_commons:WithJsonExample" - } - ], - "properties": [ - { - "name": { + "valueType": { + "_type": "named", "name": { - "originalName": "properties", + "originalName": "ExampleTypeReference", "camelCase": { - "unsafeName": "properties", - "safeName": "properties" + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" }, "snakeCase": { - "unsafeName": "properties", - "safeName": "properties" + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" }, "screamingSnakeCase": { - "unsafeName": "PROPERTIES", - "safeName": "PROPERTIES" + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "Properties", - "safeName": "Properties" + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" } }, - "wireValue": "properties" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "ExampleInlinedRequestBodyProperty", + "fernFilepath": { + "allParts": [ + { + "originalName": "types", "camelCase": { - "unsafeName": "exampleInlinedRequestBodyProperty", - "safeName": "exampleInlinedRequestBodyProperty" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "example_inlined_request_body_property", - "safeName": "example_inlined_request_body_property" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY", - "safeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "ExampleInlinedRequestBodyProperty", - "safeName": "ExampleInlinedRequestBodyProperty" + "unsafeName": "Types", + "safeName": "Types" } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } - ], - "packagePath": [], - "file": { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } + "snakeCase": { + "unsafeName": "types", + "safeName": "types" }, - "typeId": "type_http:ExampleInlinedRequestBodyProperty" + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } } - } + }, + "typeId": "type_types:ExampleTypeReference" }, "availability": null, "docs": null @@ -22322,12 +22947,11 @@ ] }, "referencedTypes": [ - "type_commons:WithJsonExample", - "type_http:ExampleInlinedRequestBodyProperty", "type_commons:NameAndWireValue", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", "type_types:ExampleTypeReferenceShape", "type_types:ExamplePrimitive", "type_commons:EscapedString", @@ -22352,25 +22976,25 @@ "availability": null, "docs": null }, - "type_http:ExampleInlinedRequestBodyProperty": { + "type_http:ExampleHeader": { "name": { "name": { - "originalName": "ExampleInlinedRequestBodyProperty", + "originalName": "ExampleHeader", "camelCase": { - "unsafeName": "exampleInlinedRequestBodyProperty", - "safeName": "exampleInlinedRequestBodyProperty" + "unsafeName": "exampleHeader", + "safeName": "exampleHeader" }, "snakeCase": { - "unsafeName": "example_inlined_request_body_property", - "safeName": "example_inlined_request_body_property" + "unsafeName": "example_header", + "safeName": "example_header" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY", - "safeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY" + "unsafeName": "EXAMPLE_HEADER", + "safeName": "EXAMPLE_HEADER" }, "pascalCase": { - "unsafeName": "ExampleInlinedRequestBodyProperty", - "safeName": "ExampleInlinedRequestBodyProperty" + "unsafeName": "ExampleHeader", + "safeName": "ExampleHeader" } }, "fernFilepath": { @@ -22416,7 +23040,7 @@ } } }, - "typeId": "type_http:ExampleInlinedRequestBodyProperty" + "typeId": "type_http:ExampleHeader" }, "shape": { "_type": "object", @@ -22605,104 +23229,6 @@ }, "availability": null, "docs": null - }, - { - "name": { - "name": { - "originalName": "originalTypeDeclaration", - "camelCase": { - "unsafeName": "originalTypeDeclaration", - "safeName": "originalTypeDeclaration" - }, - "snakeCase": { - "unsafeName": "original_type_declaration", - "safeName": "original_type_declaration" - }, - "screamingSnakeCase": { - "unsafeName": "ORIGINAL_TYPE_DECLARATION", - "safeName": "ORIGINAL_TYPE_DECLARATION" - }, - "pascalCase": { - "unsafeName": "OriginalTypeDeclaration", - "safeName": "OriginalTypeDeclaration" - } - }, - "wireValue": "originalTypeDeclaration" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "DeclaredTypeName", - "camelCase": { - "unsafeName": "declaredTypeName", - "safeName": "declaredTypeName" - }, - "snakeCase": { - "unsafeName": "declared_type_name", - "safeName": "declared_type_name" - }, - "screamingSnakeCase": { - "unsafeName": "DECLARED_TYPE_NAME", - "safeName": "DECLARED_TYPE_NAME" - }, - "pascalCase": { - "unsafeName": "DeclaredTypeName", - "safeName": "DeclaredTypeName" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:DeclaredTypeName" - } - } - }, - "availability": null, - "docs": "This property may have been brought in via extension. originalTypeDeclaration\nis the name of the type that contains this property" } ] }, @@ -22736,25 +23262,25 @@ "availability": null, "docs": null }, - "type_http:ExampleResponse": { + "type_http:ExampleRequestBody": { "name": { "name": { - "originalName": "ExampleResponse", + "originalName": "ExampleRequestBody", "camelCase": { - "unsafeName": "exampleResponse", - "safeName": "exampleResponse" + "unsafeName": "exampleRequestBody", + "safeName": "exampleRequestBody" }, "snakeCase": { - "unsafeName": "example_response", - "safeName": "example_response" + "unsafeName": "example_request_body", + "safeName": "example_request_body" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_RESPONSE", - "safeName": "EXAMPLE_RESPONSE" + "unsafeName": "EXAMPLE_REQUEST_BODY", + "safeName": "EXAMPLE_REQUEST_BODY" }, "pascalCase": { - "unsafeName": "ExampleResponse", - "safeName": "ExampleResponse" + "unsafeName": "ExampleRequestBody", + "safeName": "ExampleRequestBody" } }, "fernFilepath": { @@ -22800,7 +23326,7 @@ } } }, - "typeId": "type_http:ExampleResponse" + "typeId": "type_http:ExampleRequestBody" }, "shape": { "_type": "union", @@ -22832,45 +23358,45 @@ { "discriminantValue": { "name": { - "originalName": "ok", + "originalName": "inlinedRequestBody", "camelCase": { - "unsafeName": "ok", - "safeName": "ok" + "unsafeName": "inlinedRequestBody", + "safeName": "inlinedRequestBody" }, "snakeCase": { - "unsafeName": "ok", - "safeName": "ok" + "unsafeName": "inlined_request_body", + "safeName": "inlined_request_body" }, "screamingSnakeCase": { - "unsafeName": "OK", - "safeName": "OK" + "unsafeName": "INLINED_REQUEST_BODY", + "safeName": "INLINED_REQUEST_BODY" }, "pascalCase": { - "unsafeName": "Ok", - "safeName": "Ok" + "unsafeName": "InlinedRequestBody", + "safeName": "InlinedRequestBody" } }, - "wireValue": "ok" + "wireValue": "inlinedRequestBody" }, "shape": { "_type": "samePropertiesAsObject", "name": { - "originalName": "ExampleEndpointSuccessResponse", + "originalName": "ExampleInlinedRequestBody", "camelCase": { - "unsafeName": "exampleEndpointSuccessResponse", - "safeName": "exampleEndpointSuccessResponse" + "unsafeName": "exampleInlinedRequestBody", + "safeName": "exampleInlinedRequestBody" }, "snakeCase": { - "unsafeName": "example_endpoint_success_response", - "safeName": "example_endpoint_success_response" + "unsafeName": "example_inlined_request_body", + "safeName": "example_inlined_request_body" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE", - "safeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE" + "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY", + "safeName": "EXAMPLE_INLINED_REQUEST_BODY" }, "pascalCase": { - "unsafeName": "ExampleEndpointSuccessResponse", - "safeName": "ExampleEndpointSuccessResponse" + "unsafeName": "ExampleInlinedRequestBody", + "safeName": "ExampleInlinedRequestBody" } }, "fernFilepath": { @@ -22916,107 +23442,111 @@ } } }, - "typeId": "type_http:ExampleEndpointSuccessResponse" + "typeId": "type_http:ExampleInlinedRequestBody" }, "docs": null }, { "discriminantValue": { "name": { - "originalName": "error", + "originalName": "reference", "camelCase": { - "unsafeName": "error", - "safeName": "error" + "unsafeName": "reference", + "safeName": "reference" }, "snakeCase": { - "unsafeName": "error", - "safeName": "error" + "unsafeName": "reference", + "safeName": "reference" }, "screamingSnakeCase": { - "unsafeName": "ERROR", - "safeName": "ERROR" + "unsafeName": "REFERENCE", + "safeName": "REFERENCE" }, "pascalCase": { - "unsafeName": "Error", - "safeName": "Error" + "unsafeName": "Reference", + "safeName": "Reference" } }, - "wireValue": "error" + "wireValue": "reference" }, "shape": { "_type": "samePropertiesAsObject", "name": { - "originalName": "ExampleEndpointErrorResponse", + "originalName": "ExampleTypeReference", "camelCase": { - "unsafeName": "exampleEndpointErrorResponse", - "safeName": "exampleEndpointErrorResponse" + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" }, "snakeCase": { - "unsafeName": "example_endpoint_error_response", - "safeName": "example_endpoint_error_response" + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE", - "safeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE" + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "ExampleEndpointErrorResponse", - "safeName": "ExampleEndpointErrorResponse" + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" } }, "fernFilepath": { "allParts": [ { - "originalName": "http", + "originalName": "types", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Types", + "safeName": "Types" } } ], "packagePath": [], "file": { - "originalName": "http", + "originalName": "types", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Types", + "safeName": "Types" } } }, - "typeId": "type_http:ExampleEndpointErrorResponse" + "typeId": "type_types:ExampleTypeReference" }, "docs": null } ] }, "referencedTypes": [ - "type_http:ExampleEndpointSuccessResponse", - "type_types:ExampleTypeReference", + "type_http:ExampleInlinedRequestBody", "type_commons:WithJsonExample", + "type_http:ExampleInlinedRequestBodyProperty", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeReference", "type_types:ExampleTypeReferenceShape", "type_types:ExamplePrimitive", "type_commons:EscapedString", @@ -23026,46 +23556,40 @@ "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", "type_types:ExampleTypeShape", "type_types:ExampleAliasType", "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", "type_types:ExampleObjectType", "type_types:ExampleObjectProperty", "type_types:ExampleUnionType", "type_types:ExampleSingleUnionType", "type_types:ExampleSingleUnionTypeProperties", "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType", - "type_http:ExampleEndpointErrorResponse", - "type_errors:DeclaredErrorName", - "type_commons:ErrorId" + "type_types:ExampleUndiscriminatedUnionType" ], "examples": [], "availability": null, "docs": null }, - "type_http:ExampleEndpointSuccessResponse": { + "type_http:ExampleInlinedRequestBody": { "name": { "name": { - "originalName": "ExampleEndpointSuccessResponse", + "originalName": "ExampleInlinedRequestBody", "camelCase": { - "unsafeName": "exampleEndpointSuccessResponse", - "safeName": "exampleEndpointSuccessResponse" + "unsafeName": "exampleInlinedRequestBody", + "safeName": "exampleInlinedRequestBody" }, "snakeCase": { - "unsafeName": "example_endpoint_success_response", - "safeName": "example_endpoint_success_response" + "unsafeName": "example_inlined_request_body", + "safeName": "example_inlined_request_body" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE", - "safeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE" + "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY", + "safeName": "EXAMPLE_INLINED_REQUEST_BODY" }, "pascalCase": { - "unsafeName": "ExampleEndpointSuccessResponse", - "safeName": "ExampleEndpointSuccessResponse" + "unsafeName": "ExampleInlinedRequestBody", + "safeName": "ExampleInlinedRequestBody" } }, "fernFilepath": { @@ -23111,104 +23635,170 @@ } } }, - "typeId": "type_http:ExampleEndpointSuccessResponse" + "typeId": "type_http:ExampleInlinedRequestBody" }, "shape": { "_type": "object", - "extends": [], - "properties": [ + "extends": [ { "name": { - "name": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" + "originalName": "WithJsonExample", + "camelCase": { + "unsafeName": "withJsonExample", + "safeName": "withJsonExample" + }, + "snakeCase": { + "unsafeName": "with_json_example", + "safeName": "with_json_example" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_JSON_EXAMPLE", + "safeName": "WITH_JSON_EXAMPLE" + }, + "pascalCase": { + "unsafeName": "WithJsonExample", + "safeName": "WithJsonExample" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "body", - "safeName": "body" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithJsonExample" + } + ], + "properties": [ + { + "name": { + "name": { + "originalName": "properties", + "camelCase": { + "unsafeName": "properties", + "safeName": "properties" + }, + "snakeCase": { + "unsafeName": "properties", + "safeName": "properties" + }, + "screamingSnakeCase": { + "unsafeName": "PROPERTIES", + "safeName": "PROPERTIES" + }, + "pascalCase": { + "unsafeName": "Properties", + "safeName": "Properties" } }, - "wireValue": "body" + "wireValue": "properties" }, "valueType": { "_type": "container", "container": { - "_type": "optional", - "optional": { + "_type": "list", + "list": { "_type": "named", "name": { - "originalName": "ExampleTypeReference", + "originalName": "ExampleInlinedRequestBodyProperty", "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" + "unsafeName": "exampleInlinedRequestBodyProperty", + "safeName": "exampleInlinedRequestBodyProperty" }, "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" + "unsafeName": "example_inlined_request_body_property", + "safeName": "example_inlined_request_body_property" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" + "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY", + "safeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY" }, "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" + "unsafeName": "ExampleInlinedRequestBodyProperty", + "safeName": "ExampleInlinedRequestBodyProperty" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "http", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "http", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_types:ExampleTypeReference" + "typeId": "type_http:ExampleInlinedRequestBodyProperty" } } }, @@ -23218,8 +23808,12 @@ ] }, "referencedTypes": [ - "type_types:ExampleTypeReference", "type_commons:WithJsonExample", + "type_http:ExampleInlinedRequestBodyProperty", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeReference", "type_types:ExampleTypeReferenceShape", "type_types:ExamplePrimitive", "type_commons:EscapedString", @@ -23229,12 +23823,9 @@ "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", "type_types:ExampleTypeShape", "type_types:ExampleAliasType", "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", "type_types:ExampleObjectType", "type_types:ExampleObjectProperty", "type_types:ExampleUnionType", @@ -23247,25 +23838,25 @@ "availability": null, "docs": null }, - "type_http:ExampleEndpointErrorResponse": { + "type_http:ExampleInlinedRequestBodyProperty": { "name": { "name": { - "originalName": "ExampleEndpointErrorResponse", + "originalName": "ExampleInlinedRequestBodyProperty", "camelCase": { - "unsafeName": "exampleEndpointErrorResponse", - "safeName": "exampleEndpointErrorResponse" + "unsafeName": "exampleInlinedRequestBodyProperty", + "safeName": "exampleInlinedRequestBodyProperty" }, "snakeCase": { - "unsafeName": "example_endpoint_error_response", - "safeName": "example_endpoint_error_response" + "unsafeName": "example_inlined_request_body_property", + "safeName": "example_inlined_request_body_property" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE", - "safeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE" + "unsafeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY", + "safeName": "EXAMPLE_INLINED_REQUEST_BODY_PROPERTY" }, "pascalCase": { - "unsafeName": "ExampleEndpointErrorResponse", - "safeName": "ExampleEndpointErrorResponse" + "unsafeName": "ExampleInlinedRequestBodyProperty", + "safeName": "ExampleInlinedRequestBodyProperty" } }, "fernFilepath": { @@ -23311,7 +23902,7 @@ } } }, - "typeId": "type_http:ExampleEndpointErrorResponse" + "typeId": "type_http:ExampleInlinedRequestBodyProperty" }, "shape": { "_type": "object", @@ -23320,91 +23911,91 @@ { "name": { "name": { - "originalName": "error", + "originalName": "name", "camelCase": { - "unsafeName": "error", - "safeName": "error" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "error", - "safeName": "error" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "ERROR", - "safeName": "ERROR" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Error", - "safeName": "Error" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "error" + "wireValue": "name" }, "valueType": { "_type": "named", "name": { - "originalName": "DeclaredErrorName", + "originalName": "NameAndWireValue", "camelCase": { - "unsafeName": "declaredErrorName", - "safeName": "declaredErrorName" + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" }, "snakeCase": { - "unsafeName": "declared_error_name", - "safeName": "declared_error_name" + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" }, "screamingSnakeCase": { - "unsafeName": "DECLARED_ERROR_NAME", - "safeName": "DECLARED_ERROR_NAME" + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" }, "pascalCase": { - "unsafeName": "DeclaredErrorName", - "safeName": "DeclaredErrorName" + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" } }, "fernFilepath": { "allParts": [ { - "originalName": "errors", + "originalName": "commons", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "errors", + "originalName": "commons", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_errors:DeclaredErrorName" + "typeId": "type_commons:NameAndWireValue" }, "availability": null, "docs": null @@ -23412,25 +24003,117 @@ { "name": { "name": { - "originalName": "body", + "originalName": "value", "camelCase": { - "unsafeName": "body", - "safeName": "body" + "unsafeName": "value", + "safeName": "value" }, "snakeCase": { - "unsafeName": "body", - "safeName": "body" + "unsafeName": "value", + "safeName": "value" }, "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" + "unsafeName": "VALUE", + "safeName": "VALUE" }, "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" + "unsafeName": "Value", + "safeName": "Value" } }, - "wireValue": "body" + "wireValue": "value" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ExampleTypeReference", + "camelCase": { + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" + }, + "snakeCase": { + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" + }, + "pascalCase": { + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleTypeReference" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "originalTypeDeclaration", + "camelCase": { + "unsafeName": "originalTypeDeclaration", + "safeName": "originalTypeDeclaration" + }, + "snakeCase": { + "unsafeName": "original_type_declaration", + "safeName": "original_type_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "ORIGINAL_TYPE_DECLARATION", + "safeName": "ORIGINAL_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "OriginalTypeDeclaration", + "safeName": "OriginalTypeDeclaration" + } + }, + "wireValue": "originalTypeDeclaration" }, "valueType": { "_type": "container", @@ -23439,22 +24122,22 @@ "optional": { "_type": "named", "name": { - "originalName": "ExampleTypeReference", + "originalName": "DeclaredTypeName", "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" }, "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" }, "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" } }, "fernFilepath": { @@ -23500,19 +24183,17 @@ } } }, - "typeId": "type_types:ExampleTypeReference" + "typeId": "type_types:DeclaredTypeName" } } }, "availability": null, - "docs": null + "docs": "This property may have been brought in via extension. originalTypeDeclaration\nis the name of the type that contains this property" } ] }, "referencedTypes": [ - "type_errors:DeclaredErrorName", - "type_commons:ErrorId", - "type_commons:FernFilepath", + "type_commons:NameAndWireValue", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:ExampleTypeReference", @@ -23525,10 +24206,10 @@ "type_types:ExampleNamedType", "type_types:DeclaredTypeName", "type_commons:TypeId", + "type_commons:FernFilepath", "type_types:ExampleTypeShape", "type_types:ExampleAliasType", "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", "type_types:ExampleObjectType", "type_types:ExampleObjectProperty", "type_types:ExampleUnionType", @@ -23541,640 +24222,725 @@ "availability": null, "docs": null }, - "type_ir:IntermediateRepresentation": { + "type_http:ExampleResponse": { "name": { "name": { - "originalName": "IntermediateRepresentation", + "originalName": "ExampleResponse", "camelCase": { - "unsafeName": "intermediateRepresentation", - "safeName": "intermediateRepresentation" + "unsafeName": "exampleResponse", + "safeName": "exampleResponse" }, "snakeCase": { - "unsafeName": "intermediate_representation", - "safeName": "intermediate_representation" + "unsafeName": "example_response", + "safeName": "example_response" }, "screamingSnakeCase": { - "unsafeName": "INTERMEDIATE_REPRESENTATION", - "safeName": "INTERMEDIATE_REPRESENTATION" + "unsafeName": "EXAMPLE_RESPONSE", + "safeName": "EXAMPLE_RESPONSE" }, "pascalCase": { - "unsafeName": "IntermediateRepresentation", - "safeName": "IntermediateRepresentation" + "unsafeName": "ExampleResponse", + "safeName": "ExampleResponse" } }, "fernFilepath": { "allParts": [ { - "originalName": "ir", + "originalName": "http", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "ir", + "originalName": "http", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_ir:IntermediateRepresentation" + "typeId": "type_http:ExampleResponse" }, "shape": { - "_type": "object", + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, "extends": [], - "properties": [ + "baseProperties": [], + "types": [ { - "name": { + "discriminantValue": { "name": { - "originalName": "apiName", + "originalName": "ok", "camelCase": { - "unsafeName": "apiName", - "safeName": "apiName" + "unsafeName": "ok", + "safeName": "ok" }, "snakeCase": { - "unsafeName": "api_name", - "safeName": "api_name" + "unsafeName": "ok", + "safeName": "ok" }, "screamingSnakeCase": { - "unsafeName": "API_NAME", - "safeName": "API_NAME" + "unsafeName": "OK", + "safeName": "OK" }, "pascalCase": { - "unsafeName": "ApiName", - "safeName": "ApiName" + "unsafeName": "Ok", + "safeName": "Ok" } }, - "wireValue": "apiName" + "wireValue": "ok" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "Name", + "originalName": "ExampleEndpointSuccessResponse", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "exampleEndpointSuccessResponse", + "safeName": "exampleEndpointSuccessResponse" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "example_endpoint_success_response", + "safeName": "example_endpoint_success_response" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE", + "safeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "ExampleEndpointSuccessResponse", + "safeName": "ExampleEndpointSuccessResponse" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_commons:Name" - }, - "availability": null, - "docs": "This is the human readable unique id for the API." - }, - { - "name": { - "name": { - "originalName": "apiDisplayName", - "camelCase": { - "unsafeName": "apiDisplayName", - "safeName": "apiDisplayName" - }, - "snakeCase": { - "unsafeName": "api_display_name", - "safeName": "api_display_name" - }, - "screamingSnakeCase": { - "unsafeName": "API_DISPLAY_NAME", - "safeName": "API_DISPLAY_NAME" - }, - "pascalCase": { - "unsafeName": "ApiDisplayName", - "safeName": "ApiDisplayName" - } - }, - "wireValue": "apiDisplayName" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "apiDocs", - "camelCase": { - "unsafeName": "apiDocs", - "safeName": "apiDocs" - }, - "snakeCase": { - "unsafeName": "api_docs", - "safeName": "api_docs" - }, - "screamingSnakeCase": { - "unsafeName": "API_DOCS", - "safeName": "API_DOCS" - }, - "pascalCase": { - "unsafeName": "ApiDocs", - "safeName": "ApiDocs" - } - }, - "wireValue": "apiDocs" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } + "typeId": "type_http:ExampleEndpointSuccessResponse" }, - "availability": null, "docs": null }, { - "name": { + "discriminantValue": { "name": { - "originalName": "auth", + "originalName": "error", "camelCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "error", + "safeName": "error" }, "snakeCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "error", + "safeName": "error" }, "screamingSnakeCase": { - "unsafeName": "AUTH", - "safeName": "AUTH" + "unsafeName": "ERROR", + "safeName": "ERROR" }, "pascalCase": { - "unsafeName": "Auth", - "safeName": "Auth" + "unsafeName": "Error", + "safeName": "Error" } }, - "wireValue": "auth" + "wireValue": "error" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "ApiAuth", + "originalName": "ExampleEndpointErrorResponse", "camelCase": { - "unsafeName": "apiAuth", - "safeName": "apiAuth" + "unsafeName": "exampleEndpointErrorResponse", + "safeName": "exampleEndpointErrorResponse" }, "snakeCase": { - "unsafeName": "api_auth", - "safeName": "api_auth" + "unsafeName": "example_endpoint_error_response", + "safeName": "example_endpoint_error_response" }, "screamingSnakeCase": { - "unsafeName": "API_AUTH", - "safeName": "API_AUTH" + "unsafeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE", + "safeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE" }, "pascalCase": { - "unsafeName": "ApiAuth", - "safeName": "ApiAuth" + "unsafeName": "ExampleEndpointErrorResponse", + "safeName": "ExampleEndpointErrorResponse" } }, "fernFilepath": { "allParts": [ { - "originalName": "auth", + "originalName": "http", "camelCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "AUTH", - "safeName": "AUTH" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Auth", - "safeName": "Auth" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "auth", + "originalName": "http", "camelCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "auth", - "safeName": "auth" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "AUTH", - "safeName": "AUTH" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Auth", - "safeName": "Auth" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_auth:ApiAuth" + "typeId": "type_http:ExampleEndpointErrorResponse" }, - "availability": null, "docs": null + } + ] + }, + "referencedTypes": [ + "type_http:ExampleEndpointSuccessResponse", + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType", + "type_http:ExampleEndpointErrorResponse", + "type_errors:DeclaredErrorName", + "type_commons:ErrorId" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_http:ExampleEndpointSuccessResponse": { + "name": { + "name": { + "originalName": "ExampleEndpointSuccessResponse", + "camelCase": { + "unsafeName": "exampleEndpointSuccessResponse", + "safeName": "exampleEndpointSuccessResponse" + }, + "snakeCase": { + "unsafeName": "example_endpoint_success_response", + "safeName": "example_endpoint_success_response" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE", + "safeName": "EXAMPLE_ENDPOINT_SUCCESS_RESPONSE" }, + "pascalCase": { + "unsafeName": "ExampleEndpointSuccessResponse", + "safeName": "ExampleEndpointSuccessResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:ExampleEndpointSuccessResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ { "name": { "name": { - "originalName": "headers", + "originalName": "body", "camelCase": { - "unsafeName": "headers", - "safeName": "headers" + "unsafeName": "body", + "safeName": "body" }, "snakeCase": { - "unsafeName": "headers", - "safeName": "headers" + "unsafeName": "body", + "safeName": "body" }, "screamingSnakeCase": { - "unsafeName": "HEADERS", - "safeName": "HEADERS" + "unsafeName": "BODY", + "safeName": "BODY" }, "pascalCase": { - "unsafeName": "Headers", - "safeName": "Headers" + "unsafeName": "Body", + "safeName": "Body" } }, - "wireValue": "headers" + "wireValue": "body" }, "valueType": { "_type": "container", "container": { - "_type": "list", - "list": { + "_type": "optional", + "optional": { "_type": "named", "name": { - "originalName": "HttpHeader", + "originalName": "ExampleTypeReference", "camelCase": { - "unsafeName": "httpHeader", - "safeName": "httpHeader" + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" }, "snakeCase": { - "unsafeName": "http_header", - "safeName": "http_header" + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" }, "screamingSnakeCase": { - "unsafeName": "HTTP_HEADER", - "safeName": "HTTP_HEADER" + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "HttpHeader", - "safeName": "HttpHeader" + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" } }, "fernFilepath": { "allParts": [ { - "originalName": "http", + "originalName": "types", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Types", + "safeName": "Types" } } ], "packagePath": [], "file": { - "originalName": "http", + "originalName": "types", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Types", + "safeName": "Types" } } }, - "typeId": "type_http:HttpHeader" + "typeId": "type_types:ExampleTypeReference" } } }, "availability": null, - "docs": "API Wide headers that are sent on every request" + "docs": null + } + ] + }, + "referencedTypes": [ + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_http:ExampleEndpointErrorResponse": { + "name": { + "name": { + "originalName": "ExampleEndpointErrorResponse", + "camelCase": { + "unsafeName": "exampleEndpointErrorResponse", + "safeName": "exampleEndpointErrorResponse" }, + "snakeCase": { + "unsafeName": "example_endpoint_error_response", + "safeName": "example_endpoint_error_response" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE", + "safeName": "EXAMPLE_ENDPOINT_ERROR_RESPONSE" + }, + "pascalCase": { + "unsafeName": "ExampleEndpointErrorResponse", + "safeName": "ExampleEndpointErrorResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:ExampleEndpointErrorResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ { "name": { "name": { - "originalName": "idempotencyHeaders", + "originalName": "error", "camelCase": { - "unsafeName": "idempotencyHeaders", - "safeName": "idempotencyHeaders" + "unsafeName": "error", + "safeName": "error" }, "snakeCase": { - "unsafeName": "idempotency_headers", - "safeName": "idempotency_headers" + "unsafeName": "error", + "safeName": "error" }, "screamingSnakeCase": { - "unsafeName": "IDEMPOTENCY_HEADERS", - "safeName": "IDEMPOTENCY_HEADERS" + "unsafeName": "ERROR", + "safeName": "ERROR" }, "pascalCase": { - "unsafeName": "IdempotencyHeaders", - "safeName": "IdempotencyHeaders" + "unsafeName": "Error", + "safeName": "Error" } }, - "wireValue": "idempotencyHeaders" + "wireValue": "error" }, "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "HttpHeader", + "_type": "named", + "name": { + "originalName": "DeclaredErrorName", + "camelCase": { + "unsafeName": "declaredErrorName", + "safeName": "declaredErrorName" + }, + "snakeCase": { + "unsafeName": "declared_error_name", + "safeName": "declared_error_name" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARED_ERROR_NAME", + "safeName": "DECLARED_ERROR_NAME" + }, + "pascalCase": { + "unsafeName": "DeclaredErrorName", + "safeName": "DeclaredErrorName" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "errors", "camelCase": { - "unsafeName": "httpHeader", - "safeName": "httpHeader" + "unsafeName": "errors", + "safeName": "errors" }, "snakeCase": { - "unsafeName": "http_header", - "safeName": "http_header" + "unsafeName": "errors", + "safeName": "errors" }, "screamingSnakeCase": { - "unsafeName": "HTTP_HEADER", - "safeName": "HTTP_HEADER" + "unsafeName": "ERRORS", + "safeName": "ERRORS" }, "pascalCase": { - "unsafeName": "HttpHeader", - "safeName": "HttpHeader" + "unsafeName": "Errors", + "safeName": "Errors" } + } + ], + "packagePath": [], + "file": { + "originalName": "errors", + "camelCase": { + "unsafeName": "errors", + "safeName": "errors" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } - ], - "packagePath": [], - "file": { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } + "snakeCase": { + "unsafeName": "errors", + "safeName": "errors" }, - "typeId": "type_http:HttpHeader" + "screamingSnakeCase": { + "unsafeName": "ERRORS", + "safeName": "ERRORS" + }, + "pascalCase": { + "unsafeName": "Errors", + "safeName": "Errors" + } } - } + }, + "typeId": "type_errors:DeclaredErrorName" }, "availability": null, - "docs": "Headers that are sent for idempotent endpoints" + "docs": null }, { "name": { "name": { - "originalName": "types", + "originalName": "body", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "body", + "safeName": "body" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "body", + "safeName": "body" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "BODY", + "safeName": "BODY" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Body", + "safeName": "Body" } }, - "wireValue": "types" + "wireValue": "body" }, "valueType": { "_type": "container", "container": { - "_type": "map", - "keyType": { - "_type": "named", - "name": { - "originalName": "TypeId", - "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" - }, - "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" - }, - "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:TypeId" - }, - "valueType": { + "_type": "optional", + "optional": { "_type": "named", "name": { - "originalName": "TypeDeclaration", + "originalName": "ExampleTypeReference", "camelCase": { - "unsafeName": "typeDeclaration", - "safeName": "typeDeclaration" + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" }, "snakeCase": { - "unsafeName": "type_declaration", - "safeName": "type_declaration" + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" }, "screamingSnakeCase": { - "unsafeName": "TYPE_DECLARATION", - "safeName": "TYPE_DECLARATION" + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "TypeDeclaration", - "safeName": "TypeDeclaration" + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" } }, "fernFilepath": { @@ -24220,363 +24986,591 @@ } } }, - "typeId": "type_types:TypeDeclaration" + "typeId": "type_types:ExampleTypeReference" } } }, "availability": null, - "docs": "The types described by this API" + "docs": null + } + ] + }, + "referencedTypes": [ + "type_errors:DeclaredErrorName", + "type_commons:ErrorId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_ir:IntermediateRepresentation": { + "name": { + "name": { + "originalName": "IntermediateRepresentation", + "camelCase": { + "unsafeName": "intermediateRepresentation", + "safeName": "intermediateRepresentation" + }, + "snakeCase": { + "unsafeName": "intermediate_representation", + "safeName": "intermediate_representation" + }, + "screamingSnakeCase": { + "unsafeName": "INTERMEDIATE_REPRESENTATION", + "safeName": "INTERMEDIATE_REPRESENTATION" }, + "pascalCase": { + "unsafeName": "IntermediateRepresentation", + "safeName": "IntermediateRepresentation" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:IntermediateRepresentation" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ { "name": { "name": { - "originalName": "services", + "originalName": "apiName", "camelCase": { - "unsafeName": "services", - "safeName": "services" + "unsafeName": "apiName", + "safeName": "apiName" }, "snakeCase": { - "unsafeName": "services", - "safeName": "services" + "unsafeName": "api_name", + "safeName": "api_name" }, "screamingSnakeCase": { - "unsafeName": "SERVICES", - "safeName": "SERVICES" + "unsafeName": "API_NAME", + "safeName": "API_NAME" }, "pascalCase": { - "unsafeName": "Services", - "safeName": "Services" + "unsafeName": "ApiName", + "safeName": "ApiName" } }, - "wireValue": "services" + "wireValue": "apiName" }, "valueType": { - "_type": "container", - "container": { - "_type": "map", - "keyType": { - "_type": "named", - "name": { - "originalName": "ServiceId", + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", "camelCase": { - "unsafeName": "serviceId", - "safeName": "serviceId" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "service_id", - "safeName": "service_id" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "SERVICE_ID", - "safeName": "SERVICE_ID" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "ServiceId", - "safeName": "ServiceId" + "unsafeName": "Commons", + "safeName": "Commons" } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" }, - "typeId": "type_commons:ServiceId" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "HttpService", - "camelCase": { - "unsafeName": "httpService", - "safeName": "httpService" - }, - "snakeCase": { - "unsafeName": "http_service", - "safeName": "http_service" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP_SERVICE", - "safeName": "HTTP_SERVICE" - }, - "pascalCase": { - "unsafeName": "HttpService", - "safeName": "HttpService" - } + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } - ], - "packagePath": [], - "file": { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" - }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" - }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" - }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Name" + }, + "availability": null, + "docs": "This is the human readable unique id for the API." + }, + { + "name": { + "name": { + "originalName": "apiDisplayName", + "camelCase": { + "unsafeName": "apiDisplayName", + "safeName": "apiDisplayName" + }, + "snakeCase": { + "unsafeName": "api_display_name", + "safeName": "api_display_name" + }, + "screamingSnakeCase": { + "unsafeName": "API_DISPLAY_NAME", + "safeName": "API_DISPLAY_NAME" + }, + "pascalCase": { + "unsafeName": "ApiDisplayName", + "safeName": "ApiDisplayName" + } + }, + "wireValue": "apiDisplayName" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": "STRING" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "apiDocs", + "camelCase": { + "unsafeName": "apiDocs", + "safeName": "apiDocs" + }, + "snakeCase": { + "unsafeName": "api_docs", + "safeName": "api_docs" + }, + "screamingSnakeCase": { + "unsafeName": "API_DOCS", + "safeName": "API_DOCS" + }, + "pascalCase": { + "unsafeName": "ApiDocs", + "safeName": "ApiDocs" + } + }, + "wireValue": "apiDocs" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": "STRING" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "auth", + "camelCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "snakeCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "screamingSnakeCase": { + "unsafeName": "AUTH", + "safeName": "AUTH" + }, + "pascalCase": { + "unsafeName": "Auth", + "safeName": "Auth" + } + }, + "wireValue": "auth" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ApiAuth", + "camelCase": { + "unsafeName": "apiAuth", + "safeName": "apiAuth" + }, + "snakeCase": { + "unsafeName": "api_auth", + "safeName": "api_auth" + }, + "screamingSnakeCase": { + "unsafeName": "API_AUTH", + "safeName": "API_AUTH" + }, + "pascalCase": { + "unsafeName": "ApiAuth", + "safeName": "ApiAuth" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "auth", + "camelCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "snakeCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "screamingSnakeCase": { + "unsafeName": "AUTH", + "safeName": "AUTH" + }, + "pascalCase": { + "unsafeName": "Auth", + "safeName": "Auth" } + } + ], + "packagePath": [], + "file": { + "originalName": "auth", + "camelCase": { + "unsafeName": "auth", + "safeName": "auth" }, - "typeId": "type_http:HttpService" + "snakeCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "screamingSnakeCase": { + "unsafeName": "AUTH", + "safeName": "AUTH" + }, + "pascalCase": { + "unsafeName": "Auth", + "safeName": "Auth" + } } - } + }, + "typeId": "type_auth:ApiAuth" }, "availability": null, - "docs": "The services exposed by this API" + "docs": null }, { "name": { "name": { - "originalName": "webhookGroups", + "originalName": "headers", "camelCase": { - "unsafeName": "webhookGroups", - "safeName": "webhookGroups" + "unsafeName": "headers", + "safeName": "headers" }, "snakeCase": { - "unsafeName": "webhook_groups", - "safeName": "webhook_groups" + "unsafeName": "headers", + "safeName": "headers" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_GROUPS", - "safeName": "WEBHOOK_GROUPS" + "unsafeName": "HEADERS", + "safeName": "HEADERS" }, "pascalCase": { - "unsafeName": "WebhookGroups", - "safeName": "WebhookGroups" + "unsafeName": "Headers", + "safeName": "Headers" } }, - "wireValue": "webhookGroups" + "wireValue": "headers" }, "valueType": { "_type": "container", "container": { - "_type": "map", - "keyType": { + "_type": "list", + "list": { "_type": "named", "name": { - "originalName": "WebhookGroupId", + "originalName": "HttpHeader", "camelCase": { - "unsafeName": "webhookGroupId", - "safeName": "webhookGroupId" + "unsafeName": "httpHeader", + "safeName": "httpHeader" }, "snakeCase": { - "unsafeName": "webhook_group_id", - "safeName": "webhook_group_id" + "unsafeName": "http_header", + "safeName": "http_header" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_GROUP_ID", - "safeName": "WEBHOOK_GROUP_ID" + "unsafeName": "HTTP_HEADER", + "safeName": "HTTP_HEADER" }, "pascalCase": { - "unsafeName": "WebhookGroupId", - "safeName": "WebhookGroupId" + "unsafeName": "HttpHeader", + "safeName": "HttpHeader" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "http", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_commons:WebhookGroupId" + "typeId": "type_http:HttpHeader" + } + } + }, + "availability": null, + "docs": "API Wide headers that are sent on every request" + }, + { + "name": { + "name": { + "originalName": "idempotencyHeaders", + "camelCase": { + "unsafeName": "idempotencyHeaders", + "safeName": "idempotencyHeaders" }, - "valueType": { + "snakeCase": { + "unsafeName": "idempotency_headers", + "safeName": "idempotency_headers" + }, + "screamingSnakeCase": { + "unsafeName": "IDEMPOTENCY_HEADERS", + "safeName": "IDEMPOTENCY_HEADERS" + }, + "pascalCase": { + "unsafeName": "IdempotencyHeaders", + "safeName": "IdempotencyHeaders" + } + }, + "wireValue": "idempotencyHeaders" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { "_type": "named", "name": { - "originalName": "WebhookGroup", + "originalName": "HttpHeader", "camelCase": { - "unsafeName": "webhookGroup", - "safeName": "webhookGroup" + "unsafeName": "httpHeader", + "safeName": "httpHeader" }, "snakeCase": { - "unsafeName": "webhook_group", - "safeName": "webhook_group" + "unsafeName": "http_header", + "safeName": "http_header" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_GROUP", - "safeName": "WEBHOOK_GROUP" + "unsafeName": "HTTP_HEADER", + "safeName": "HTTP_HEADER" }, "pascalCase": { - "unsafeName": "WebhookGroup", - "safeName": "WebhookGroup" + "unsafeName": "HttpHeader", + "safeName": "HttpHeader" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "http", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "http", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_webhooks:WebhookGroup" + "typeId": "type_http:HttpHeader" } } }, "availability": null, - "docs": "The webhooks sent by this API" + "docs": "Headers that are sent for idempotent endpoints" }, { "name": { "name": { - "originalName": "errors", + "originalName": "types", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "Types", + "safeName": "Types" } }, - "wireValue": "errors" + "wireValue": "types" }, "valueType": { "_type": "container", @@ -24585,22 +25579,22 @@ "keyType": { "_type": "named", "name": { - "originalName": "ErrorId", + "originalName": "TypeId", "camelCase": { - "unsafeName": "errorId", - "safeName": "errorId" + "unsafeName": "typeId", + "safeName": "typeId" }, "snakeCase": { - "unsafeName": "error_id", - "safeName": "error_id" + "unsafeName": "type_id", + "safeName": "type_id" }, "screamingSnakeCase": { - "unsafeName": "ERROR_ID", - "safeName": "ERROR_ID" + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" }, "pascalCase": { - "unsafeName": "ErrorId", - "safeName": "ErrorId" + "unsafeName": "TypeId", + "safeName": "TypeId" } }, "fernFilepath": { @@ -24646,101 +25640,101 @@ } } }, - "typeId": "type_commons:ErrorId" + "typeId": "type_commons:TypeId" }, "valueType": { "_type": "named", "name": { - "originalName": "ErrorDeclaration", + "originalName": "TypeDeclaration", "camelCase": { - "unsafeName": "errorDeclaration", - "safeName": "errorDeclaration" + "unsafeName": "typeDeclaration", + "safeName": "typeDeclaration" }, "snakeCase": { - "unsafeName": "error_declaration", - "safeName": "error_declaration" + "unsafeName": "type_declaration", + "safeName": "type_declaration" }, "screamingSnakeCase": { - "unsafeName": "ERROR_DECLARATION", - "safeName": "ERROR_DECLARATION" + "unsafeName": "TYPE_DECLARATION", + "safeName": "TYPE_DECLARATION" }, "pascalCase": { - "unsafeName": "ErrorDeclaration", - "safeName": "ErrorDeclaration" + "unsafeName": "TypeDeclaration", + "safeName": "TypeDeclaration" } }, "fernFilepath": { "allParts": [ { - "originalName": "errors", + "originalName": "types", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "Types", + "safeName": "Types" } } ], "packagePath": [], "file": { - "originalName": "errors", + "originalName": "types", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "Types", + "safeName": "Types" } } }, - "typeId": "type_errors:ErrorDeclaration" + "typeId": "type_types:TypeDeclaration" } } }, "availability": null, - "docs": null + "docs": "The types described by this API" }, { "name": { "name": { - "originalName": "subpackages", + "originalName": "services", "camelCase": { - "unsafeName": "subpackages", - "safeName": "subpackages" + "unsafeName": "services", + "safeName": "services" }, "snakeCase": { - "unsafeName": "subpackages", - "safeName": "subpackages" + "unsafeName": "services", + "safeName": "services" }, "screamingSnakeCase": { - "unsafeName": "SUBPACKAGES", - "safeName": "SUBPACKAGES" + "unsafeName": "SERVICES", + "safeName": "SERVICES" }, "pascalCase": { - "unsafeName": "Subpackages", - "safeName": "Subpackages" + "unsafeName": "Services", + "safeName": "Services" } }, - "wireValue": "subpackages" + "wireValue": "services" }, "valueType": { "_type": "container", @@ -24749,22 +25743,22 @@ "keyType": { "_type": "named", "name": { - "originalName": "SubpackageId", + "originalName": "ServiceId", "camelCase": { - "unsafeName": "subpackageId", - "safeName": "subpackageId" + "unsafeName": "serviceId", + "safeName": "serviceId" }, "snakeCase": { - "unsafeName": "subpackage_id", - "safeName": "subpackage_id" + "unsafeName": "service_id", + "safeName": "service_id" }, "screamingSnakeCase": { - "unsafeName": "SUBPACKAGE_ID", - "safeName": "SUBPACKAGE_ID" + "unsafeName": "SERVICE_ID", + "safeName": "SERVICE_ID" }, "pascalCase": { - "unsafeName": "SubpackageId", - "safeName": "SubpackageId" + "unsafeName": "ServiceId", + "safeName": "ServiceId" } }, "fernFilepath": { @@ -24810,737 +25804,573 @@ } } }, - "typeId": "type_commons:SubpackageId" + "typeId": "type_commons:ServiceId" }, "valueType": { "_type": "named", "name": { - "originalName": "Subpackage", + "originalName": "HttpService", "camelCase": { - "unsafeName": "subpackage", - "safeName": "subpackage" + "unsafeName": "httpService", + "safeName": "httpService" }, "snakeCase": { - "unsafeName": "subpackage", - "safeName": "subpackage" + "unsafeName": "http_service", + "safeName": "http_service" }, "screamingSnakeCase": { - "unsafeName": "SUBPACKAGE", - "safeName": "SUBPACKAGE" + "unsafeName": "HTTP_SERVICE", + "safeName": "HTTP_SERVICE" }, "pascalCase": { - "unsafeName": "Subpackage", - "safeName": "Subpackage" + "unsafeName": "HttpService", + "safeName": "HttpService" } }, "fernFilepath": { "allParts": [ { - "originalName": "ir", + "originalName": "http", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "ir", + "originalName": "http", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_ir:Subpackage" + "typeId": "type_http:HttpService" } } }, "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "rootPackage", - "camelCase": { - "unsafeName": "rootPackage", - "safeName": "rootPackage" - }, - "snakeCase": { - "unsafeName": "root_package", - "safeName": "root_package" - }, - "screamingSnakeCase": { - "unsafeName": "ROOT_PACKAGE", - "safeName": "ROOT_PACKAGE" - }, - "pascalCase": { - "unsafeName": "RootPackage", - "safeName": "RootPackage" - } - }, - "wireValue": "rootPackage" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "Package", - "camelCase": { - "unsafeName": "package", - "safeName": "package" - }, - "snakeCase": { - "unsafeName": "package", - "safeName": "package" - }, - "screamingSnakeCase": { - "unsafeName": "PACKAGE", - "safeName": "PACKAGE" - }, - "pascalCase": { - "unsafeName": "Package", - "safeName": "Package" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:Package" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "constants", - "camelCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "snakeCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "screamingSnakeCase": { - "unsafeName": "CONSTANTS", - "safeName": "CONSTANTS" - }, - "pascalCase": { - "unsafeName": "Constants", - "safeName": "Constants" - } - }, - "wireValue": "constants" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "Constants", - "camelCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "snakeCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "screamingSnakeCase": { - "unsafeName": "CONSTANTS", - "safeName": "CONSTANTS" - }, - "pascalCase": { - "unsafeName": "Constants", - "safeName": "Constants" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "constants", - "camelCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "snakeCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "screamingSnakeCase": { - "unsafeName": "CONSTANTS", - "safeName": "CONSTANTS" - }, - "pascalCase": { - "unsafeName": "Constants", - "safeName": "Constants" - } - } - ], - "packagePath": [], - "file": { - "originalName": "constants", - "camelCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "snakeCase": { - "unsafeName": "constants", - "safeName": "constants" - }, - "screamingSnakeCase": { - "unsafeName": "CONSTANTS", - "safeName": "CONSTANTS" - }, - "pascalCase": { - "unsafeName": "Constants", - "safeName": "Constants" - } - } - }, - "typeId": "type_constants:Constants" - }, - "availability": null, - "docs": null + "docs": "The services exposed by this API" }, { "name": { "name": { - "originalName": "environments", + "originalName": "webhookGroups", "camelCase": { - "unsafeName": "environments", - "safeName": "environments" + "unsafeName": "webhookGroups", + "safeName": "webhookGroups" }, "snakeCase": { - "unsafeName": "environments", - "safeName": "environments" + "unsafeName": "webhook_groups", + "safeName": "webhook_groups" }, "screamingSnakeCase": { - "unsafeName": "ENVIRONMENTS", - "safeName": "ENVIRONMENTS" + "unsafeName": "WEBHOOK_GROUPS", + "safeName": "WEBHOOK_GROUPS" }, "pascalCase": { - "unsafeName": "Environments", - "safeName": "Environments" + "unsafeName": "WebhookGroups", + "safeName": "WebhookGroups" } }, - "wireValue": "environments" + "wireValue": "webhookGroups" }, "valueType": { "_type": "container", "container": { - "_type": "optional", - "optional": { + "_type": "map", + "keyType": { "_type": "named", "name": { - "originalName": "EnvironmentsConfig", + "originalName": "WebhookGroupId", "camelCase": { - "unsafeName": "environmentsConfig", - "safeName": "environmentsConfig" + "unsafeName": "webhookGroupId", + "safeName": "webhookGroupId" }, "snakeCase": { - "unsafeName": "environments_config", - "safeName": "environments_config" + "unsafeName": "webhook_group_id", + "safeName": "webhook_group_id" }, "screamingSnakeCase": { - "unsafeName": "ENVIRONMENTS_CONFIG", - "safeName": "ENVIRONMENTS_CONFIG" + "unsafeName": "WEBHOOK_GROUP_ID", + "safeName": "WEBHOOK_GROUP_ID" }, "pascalCase": { - "unsafeName": "EnvironmentsConfig", - "safeName": "EnvironmentsConfig" + "unsafeName": "WebhookGroupId", + "safeName": "WebhookGroupId" } }, "fernFilepath": { "allParts": [ { - "originalName": "environment", + "originalName": "commons", "camelCase": { - "unsafeName": "environment", - "safeName": "environment" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "environment", - "safeName": "environment" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "ENVIRONMENT", - "safeName": "ENVIRONMENT" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Environment", - "safeName": "Environment" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "environment", + "originalName": "commons", "camelCase": { - "unsafeName": "environment", - "safeName": "environment" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "environment", - "safeName": "environment" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "ENVIRONMENT", - "safeName": "ENVIRONMENT" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Environment", - "safeName": "Environment" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_environment:EnvironmentsConfig" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "basePath", - "camelCase": { - "unsafeName": "basePath", - "safeName": "basePath" - }, - "snakeCase": { - "unsafeName": "base_path", - "safeName": "base_path" - }, - "screamingSnakeCase": { - "unsafeName": "BASE_PATH", - "safeName": "BASE_PATH" + "typeId": "type_commons:WebhookGroupId" }, - "pascalCase": { - "unsafeName": "BasePath", - "safeName": "BasePath" - } - }, - "wireValue": "basePath" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { + "valueType": { "_type": "named", "name": { - "originalName": "HttpPath", + "originalName": "WebhookGroup", "camelCase": { - "unsafeName": "httpPath", - "safeName": "httpPath" + "unsafeName": "webhookGroup", + "safeName": "webhookGroup" }, "snakeCase": { - "unsafeName": "http_path", - "safeName": "http_path" + "unsafeName": "webhook_group", + "safeName": "webhook_group" }, "screamingSnakeCase": { - "unsafeName": "HTTP_PATH", - "safeName": "HTTP_PATH" + "unsafeName": "WEBHOOK_GROUP", + "safeName": "WEBHOOK_GROUP" }, "pascalCase": { - "unsafeName": "HttpPath", - "safeName": "HttpPath" + "unsafeName": "WebhookGroup", + "safeName": "WebhookGroup" } }, "fernFilepath": { "allParts": [ { - "originalName": "http", + "originalName": "webhooks", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "http", + "originalName": "webhooks", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_http:HttpPath" + "typeId": "type_webhooks:WebhookGroup" } } }, "availability": null, - "docs": null + "docs": "The webhooks sent by this API" }, { "name": { "name": { - "originalName": "pathParameters", + "originalName": "websocketChannels", "camelCase": { - "unsafeName": "pathParameters", - "safeName": "pathParameters" + "unsafeName": "websocketChannels", + "safeName": "websocketChannels" }, "snakeCase": { - "unsafeName": "path_parameters", - "safeName": "path_parameters" + "unsafeName": "websocket_channels", + "safeName": "websocket_channels" }, "screamingSnakeCase": { - "unsafeName": "PATH_PARAMETERS", - "safeName": "PATH_PARAMETERS" + "unsafeName": "WEBSOCKET_CHANNELS", + "safeName": "WEBSOCKET_CHANNELS" }, "pascalCase": { - "unsafeName": "PathParameters", - "safeName": "PathParameters" + "unsafeName": "WebsocketChannels", + "safeName": "WebsocketChannels" } }, - "wireValue": "pathParameters" + "wireValue": "websocketChannels" }, "valueType": { "_type": "container", "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "PathParameter", - "camelCase": { - "unsafeName": "pathParameter", - "safeName": "pathParameter" - }, - "snakeCase": { - "unsafeName": "path_parameter", - "safeName": "path_parameter" - }, - "screamingSnakeCase": { - "unsafeName": "PATH_PARAMETER", - "safeName": "PATH_PARAMETER" - }, - "pascalCase": { - "unsafeName": "PathParameter", - "safeName": "PathParameter" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "http", + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "named", + "name": { + "originalName": "WebsocketChannelId", "camelCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "websocketChannelId", + "safeName": "websocketChannelId" }, "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "unsafeName": "websocket_channel_id", + "safeName": "websocket_channel_id" }, "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "unsafeName": "WEBSOCKET_CHANNEL_ID", + "safeName": "WEBSOCKET_CHANNEL_ID" }, "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" + "unsafeName": "WebsocketChannelId", + "safeName": "WebsocketChannelId" } - } - ], - "packagePath": [], - "file": { - "originalName": "http", - "camelCase": { - "unsafeName": "http", - "safeName": "http" }, - "snakeCase": { - "unsafeName": "http", - "safeName": "http" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "screamingSnakeCase": { - "unsafeName": "HTTP", - "safeName": "HTTP" + "typeId": "type_commons:WebsocketChannelId" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "WebsocketChannel", + "camelCase": { + "unsafeName": "websocketChannel", + "safeName": "websocketChannel" + }, + "snakeCase": { + "unsafeName": "websocket_channel", + "safeName": "websocket_channel" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_CHANNEL", + "safeName": "WEBSOCKET_CHANNEL" + }, + "pascalCase": { + "unsafeName": "WebsocketChannel", + "safeName": "WebsocketChannel" + } }, - "pascalCase": { - "unsafeName": "Http", - "safeName": "Http" - } - } - }, - "typeId": "type_http:PathParameter" + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "typeId": "type_websocket:WebsocketChannel" + } + } } } }, "availability": null, - "docs": null + "docs": "The websocket channels served by this API" }, { "name": { "name": { - "originalName": "errorDiscriminationStrategy", + "originalName": "errors", "camelCase": { - "unsafeName": "errorDiscriminationStrategy", - "safeName": "errorDiscriminationStrategy" + "unsafeName": "errors", + "safeName": "errors" }, "snakeCase": { - "unsafeName": "error_discrimination_strategy", - "safeName": "error_discrimination_strategy" + "unsafeName": "errors", + "safeName": "errors" }, "screamingSnakeCase": { - "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", - "safeName": "ERROR_DISCRIMINATION_STRATEGY" + "unsafeName": "ERRORS", + "safeName": "ERRORS" }, "pascalCase": { - "unsafeName": "ErrorDiscriminationStrategy", - "safeName": "ErrorDiscriminationStrategy" + "unsafeName": "Errors", + "safeName": "Errors" } }, - "wireValue": "errorDiscriminationStrategy" + "wireValue": "errors" }, "valueType": { - "_type": "named", - "name": { - "originalName": "ErrorDiscriminationStrategy", - "camelCase": { - "unsafeName": "errorDiscriminationStrategy", - "safeName": "errorDiscriminationStrategy" - }, - "snakeCase": { - "unsafeName": "error_discrimination_strategy", - "safeName": "error_discrimination_strategy" - }, - "screamingSnakeCase": { - "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", - "safeName": "ERROR_DISCRIMINATION_STRATEGY" - }, - "pascalCase": { - "unsafeName": "ErrorDiscriminationStrategy", - "safeName": "ErrorDiscriminationStrategy" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "named", + "name": { + "originalName": "ErrorId", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "errorId", + "safeName": "errorId" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "error_id", + "safeName": "error_id" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "ERROR_ID", + "safeName": "ERROR_ID" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "ErrorId", + "safeName": "ErrorId" } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:ErrorDiscriminationStrategy" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "sdkConfig", - "camelCase": { - "unsafeName": "sdkConfig", - "safeName": "sdkConfig" - }, - "snakeCase": { - "unsafeName": "sdk_config", - "safeName": "sdk_config" - }, - "screamingSnakeCase": { - "unsafeName": "SDK_CONFIG", - "safeName": "SDK_CONFIG" - }, - "pascalCase": { - "unsafeName": "SdkConfig", - "safeName": "SdkConfig" - } - }, - "wireValue": "sdkConfig" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "SdkConfig", - "camelCase": { - "unsafeName": "sdkConfig", - "safeName": "sdkConfig" - }, - "snakeCase": { - "unsafeName": "sdk_config", - "safeName": "sdk_config" - }, - "screamingSnakeCase": { - "unsafeName": "SDK_CONFIG", - "safeName": "SDK_CONFIG" + "typeId": "type_commons:ErrorId" }, - "pascalCase": { - "unsafeName": "SdkConfig", - "safeName": "SdkConfig" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", + "valueType": { + "_type": "named", + "name": { + "originalName": "ErrorDeclaration", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "errorDeclaration", + "safeName": "errorDeclaration" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "error_declaration", + "safeName": "error_declaration" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "ERROR_DECLARATION", + "safeName": "ERROR_DECLARATION" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "ErrorDeclaration", + "safeName": "ErrorDeclaration" } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "fernFilepath": { + "allParts": [ + { + "originalName": "errors", + "camelCase": { + "unsafeName": "errors", + "safeName": "errors" + }, + "snakeCase": { + "unsafeName": "errors", + "safeName": "errors" + }, + "screamingSnakeCase": { + "unsafeName": "ERRORS", + "safeName": "ERRORS" + }, + "pascalCase": { + "unsafeName": "Errors", + "safeName": "Errors" + } + } + ], + "packagePath": [], + "file": { + "originalName": "errors", + "camelCase": { + "unsafeName": "errors", + "safeName": "errors" + }, + "snakeCase": { + "unsafeName": "errors", + "safeName": "errors" + }, + "screamingSnakeCase": { + "unsafeName": "ERRORS", + "safeName": "ERRORS" + }, + "pascalCase": { + "unsafeName": "Errors", + "safeName": "Errors" + } + } }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } + "typeId": "type_errors:ErrorDeclaration" } - }, - "typeId": "type_ir:SdkConfig" + } }, "availability": null, "docs": null @@ -25548,95 +26378,161 @@ { "name": { "name": { - "originalName": "variables", + "originalName": "subpackages", "camelCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "subpackages", + "safeName": "subpackages" }, "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "subpackages", + "safeName": "subpackages" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "SUBPACKAGES", + "safeName": "SUBPACKAGES" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Subpackages", + "safeName": "Subpackages" } }, - "wireValue": "variables" + "wireValue": "subpackages" }, "valueType": { "_type": "container", "container": { - "_type": "list", - "list": { + "_type": "map", + "keyType": { "_type": "named", "name": { - "originalName": "VariableDeclaration", + "originalName": "SubpackageId", "camelCase": { - "unsafeName": "variableDeclaration", - "safeName": "variableDeclaration" + "unsafeName": "subpackageId", + "safeName": "subpackageId" }, "snakeCase": { - "unsafeName": "variable_declaration", - "safeName": "variable_declaration" + "unsafeName": "subpackage_id", + "safeName": "subpackage_id" }, "screamingSnakeCase": { - "unsafeName": "VARIABLE_DECLARATION", - "safeName": "VARIABLE_DECLARATION" + "unsafeName": "SUBPACKAGE_ID", + "safeName": "SUBPACKAGE_ID" }, "pascalCase": { - "unsafeName": "VariableDeclaration", - "safeName": "VariableDeclaration" + "unsafeName": "SubpackageId", + "safeName": "SubpackageId" } }, "fernFilepath": { "allParts": [ { - "originalName": "variables", + "originalName": "commons", "camelCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "variables", + "originalName": "commons", "camelCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_variables:VariableDeclaration" + "typeId": "type_commons:SubpackageId" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Subpackage", + "camelCase": { + "unsafeName": "subpackage", + "safeName": "subpackage" + }, + "snakeCase": { + "unsafeName": "subpackage", + "safeName": "subpackage" + }, + "screamingSnakeCase": { + "unsafeName": "SUBPACKAGE", + "safeName": "SUBPACKAGE" + }, + "pascalCase": { + "unsafeName": "Subpackage", + "safeName": "Subpackage" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:Subpackage" } } }, @@ -25646,45 +26542,45 @@ { "name": { "name": { - "originalName": "serviceTypeReferenceInfo", + "originalName": "rootPackage", "camelCase": { - "unsafeName": "serviceTypeReferenceInfo", - "safeName": "serviceTypeReferenceInfo" + "unsafeName": "rootPackage", + "safeName": "rootPackage" }, "snakeCase": { - "unsafeName": "service_type_reference_info", - "safeName": "service_type_reference_info" + "unsafeName": "root_package", + "safeName": "root_package" }, "screamingSnakeCase": { - "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", - "safeName": "SERVICE_TYPE_REFERENCE_INFO" + "unsafeName": "ROOT_PACKAGE", + "safeName": "ROOT_PACKAGE" }, "pascalCase": { - "unsafeName": "ServiceTypeReferenceInfo", - "safeName": "ServiceTypeReferenceInfo" + "unsafeName": "RootPackage", + "safeName": "RootPackage" } }, - "wireValue": "serviceTypeReferenceInfo" + "wireValue": "rootPackage" }, "valueType": { "_type": "named", "name": { - "originalName": "ServiceTypeReferenceInfo", + "originalName": "Package", "camelCase": { - "unsafeName": "serviceTypeReferenceInfo", - "safeName": "serviceTypeReferenceInfo" + "unsafeName": "package", + "safeName": "package" }, "snakeCase": { - "unsafeName": "service_type_reference_info", - "safeName": "service_type_reference_info" + "unsafeName": "package", + "safeName": "package" }, "screamingSnakeCase": { - "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", - "safeName": "SERVICE_TYPE_REFERENCE_INFO" + "unsafeName": "PACKAGE", + "safeName": "PACKAGE" }, "pascalCase": { - "unsafeName": "ServiceTypeReferenceInfo", - "safeName": "ServiceTypeReferenceInfo" + "unsafeName": "Package", + "safeName": "Package" } }, "fernFilepath": { @@ -25730,281 +26626,99 @@ } } }, - "typeId": "type_ir:ServiceTypeReferenceInfo" + "typeId": "type_ir:Package" }, "availability": null, "docs": null - } - ] - }, - "referencedTypes": [ - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_auth:ApiAuth", - "type_commons:WithDocs", - "type_auth:AuthSchemesRequirement", - "type_auth:AuthScheme", - "type_auth:BearerAuthScheme", - "type_auth:EnvironmentVariable", - "type_auth:BasicAuthScheme", - "type_auth:HeaderAuthScheme", - "type_commons:NameAndWireValue", - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:PrimitiveType", - "type_http:HttpHeader", - "type_commons:Declaration", - "type_commons:Availability", - "type_commons:AvailabilityStatus", - "type_types:TypeDeclaration", - "type_types:Type", - "type_types:AliasTypeDeclaration", - "type_types:ResolvedTypeReference", - "type_types:ResolvedNamedType", - "type_types:ShapeType", - "type_types:EnumTypeDeclaration", - "type_types:EnumValue", - "type_types:ObjectTypeDeclaration", - "type_types:ObjectProperty", - "type_types:UnionTypeDeclaration", - "type_types:SingleUnionType", - "type_types:SingleUnionTypeProperties", - "type_types:SingleUnionTypeProperty", - "type_types:UndiscriminatedUnionTypeDeclaration", - "type_types:UndiscriminatedUnionMember", - "type_types:ExampleType", - "type_commons:WithJsonExample", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleTypeReference", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:ExampleEnumType", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType", - "type_commons:ServiceId", - "type_http:HttpService", - "type_http:DeclaredServiceName", - "type_http:HttpPath", - "type_http:HttpPathPart", - "type_http:HttpEndpoint", - "type_commons:EndpointId", - "type_http:EndpointName", - "type_http:HttpMethod", - "type_environment:EnvironmentBaseUrlId", - "type_http:PathParameter", - "type_http:PathParameterLocation", - "type_variables:VariableId", - "type_http:QueryParameter", - "type_http:HttpRequestBody", - "type_http:InlinedRequestBody", - "type_http:InlinedRequestBodyProperty", - "type_http:HttpRequestBodyReference", - "type_http:FileUploadRequest", - "type_http:FileUploadRequestProperty", - "type_http:FileProperty", - "type_http:BytesRequest", - "type_http:SdkRequest", - "type_http:SdkRequestShape", - "type_http:SdkRequestBodyType", - "type_http:SdkRequestWrapper", - "type_http:HttpResponse", - "type_http:JsonResponse", - "type_http:JsonResponseBody", - "type_http:JsonResponseBodyWithProperty", - "type_http:FileDownloadResponse", - "type_http:TextResponse", - "type_http:StreamingResponse", - "type_http:StreamingResponseChunkType", - "type_http:ResponseErrors", - "type_http:ResponseError", - "type_errors:DeclaredErrorName", - "type_commons:ErrorId", - "type_http:ExampleEndpointCall", - "type_http:ExamplePathParameter", - "type_http:ExampleHeader", - "type_http:ExampleQueryParameter", - "type_http:ExampleRequestBody", - "type_http:ExampleInlinedRequestBody", - "type_http:ExampleInlinedRequestBodyProperty", - "type_http:ExampleResponse", - "type_http:ExampleEndpointSuccessResponse", - "type_http:ExampleEndpointErrorResponse", - "type_commons:WebhookGroupId", - "type_webhooks:WebhookGroup", - "type_webhooks:Webhook", - "type_webhooks:WebhookName", - "type_webhooks:WebhookHttpMethod", - "type_webhooks:WebhookPayload", - "type_webhooks:InlinedWebhookPayload", - "type_webhooks:InlinedWebhookPayloadProperty", - "type_webhooks:WebhookPayloadReference", - "type_errors:ErrorDeclaration", - "type_commons:SubpackageId", - "type_ir:Subpackage", - "type_ir:Package", - "type_ir:PackageNavigationConfig", - "type_constants:Constants", - "type_environment:EnvironmentsConfig", - "type_environment:EnvironmentId", - "type_environment:Environments", - "type_environment:SingleBaseUrlEnvironments", - "type_environment:SingleBaseUrlEnvironment", - "type_environment:EnvironmentUrl", - "type_environment:MultipleBaseUrlsEnvironments", - "type_environment:EnvironmentBaseUrlWithId", - "type_environment:MultipleBaseUrlsEnvironment", - "type_ir:ErrorDiscriminationStrategy", - "type_ir:ErrorDiscriminationByPropertyStrategy", - "type_ir:SdkConfig", - "type_ir:PlatformHeaders", - "type_variables:VariableDeclaration", - "type_ir:ServiceTypeReferenceInfo" - ], - "examples": [], - "availability": null, - "docs": "Complete representation of the API schema" - }, - "type_ir:SdkConfig": { - "name": { - "name": { - "originalName": "SdkConfig", - "camelCase": { - "unsafeName": "sdkConfig", - "safeName": "sdkConfig" - }, - "snakeCase": { - "unsafeName": "sdk_config", - "safeName": "sdk_config" }, - "screamingSnakeCase": { - "unsafeName": "SDK_CONFIG", - "safeName": "SDK_CONFIG" - }, - "pascalCase": { - "unsafeName": "SdkConfig", - "safeName": "SdkConfig" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:SdkConfig" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ { "name": { "name": { - "originalName": "isAuthMandatory", + "originalName": "constants", "camelCase": { - "unsafeName": "isAuthMandatory", - "safeName": "isAuthMandatory" + "unsafeName": "constants", + "safeName": "constants" }, "snakeCase": { - "unsafeName": "is_auth_mandatory", - "safeName": "is_auth_mandatory" + "unsafeName": "constants", + "safeName": "constants" }, "screamingSnakeCase": { - "unsafeName": "IS_AUTH_MANDATORY", - "safeName": "IS_AUTH_MANDATORY" + "unsafeName": "CONSTANTS", + "safeName": "CONSTANTS" }, "pascalCase": { - "unsafeName": "IsAuthMandatory", - "safeName": "IsAuthMandatory" + "unsafeName": "Constants", + "safeName": "Constants" } }, - "wireValue": "isAuthMandatory" + "wireValue": "constants" }, "valueType": { - "_type": "primitive", - "primitive": "BOOLEAN" - }, - "availability": null, - "docs": null - }, - { - "name": { + "_type": "named", "name": { - "originalName": "hasStreamingEndpoints", + "originalName": "Constants", "camelCase": { - "unsafeName": "hasStreamingEndpoints", - "safeName": "hasStreamingEndpoints" + "unsafeName": "constants", + "safeName": "constants" }, "snakeCase": { - "unsafeName": "has_streaming_endpoints", - "safeName": "has_streaming_endpoints" + "unsafeName": "constants", + "safeName": "constants" }, "screamingSnakeCase": { - "unsafeName": "HAS_STREAMING_ENDPOINTS", - "safeName": "HAS_STREAMING_ENDPOINTS" + "unsafeName": "CONSTANTS", + "safeName": "CONSTANTS" }, "pascalCase": { - "unsafeName": "HasStreamingEndpoints", - "safeName": "HasStreamingEndpoints" + "unsafeName": "Constants", + "safeName": "Constants" } }, - "wireValue": "hasStreamingEndpoints" - }, - "valueType": { - "_type": "primitive", - "primitive": "BOOLEAN" + "fernFilepath": { + "allParts": [ + { + "originalName": "constants", + "camelCase": { + "unsafeName": "constants", + "safeName": "constants" + }, + "snakeCase": { + "unsafeName": "constants", + "safeName": "constants" + }, + "screamingSnakeCase": { + "unsafeName": "CONSTANTS", + "safeName": "CONSTANTS" + }, + "pascalCase": { + "unsafeName": "Constants", + "safeName": "Constants" + } + } + ], + "packagePath": [], + "file": { + "originalName": "constants", + "camelCase": { + "unsafeName": "constants", + "safeName": "constants" + }, + "snakeCase": { + "unsafeName": "constants", + "safeName": "constants" + }, + "screamingSnakeCase": { + "unsafeName": "CONSTANTS", + "safeName": "CONSTANTS" + }, + "pascalCase": { + "unsafeName": "Constants", + "safeName": "Constants" + } + } + }, + "typeId": "type_constants:Constants" }, "availability": null, "docs": null @@ -26012,29 +26726,97 @@ { "name": { "name": { - "originalName": "hasFileDownloadEndpoints", + "originalName": "environments", "camelCase": { - "unsafeName": "hasFileDownloadEndpoints", - "safeName": "hasFileDownloadEndpoints" + "unsafeName": "environments", + "safeName": "environments" }, "snakeCase": { - "unsafeName": "has_file_download_endpoints", - "safeName": "has_file_download_endpoints" + "unsafeName": "environments", + "safeName": "environments" }, "screamingSnakeCase": { - "unsafeName": "HAS_FILE_DOWNLOAD_ENDPOINTS", - "safeName": "HAS_FILE_DOWNLOAD_ENDPOINTS" + "unsafeName": "ENVIRONMENTS", + "safeName": "ENVIRONMENTS" }, "pascalCase": { - "unsafeName": "HasFileDownloadEndpoints", - "safeName": "HasFileDownloadEndpoints" + "unsafeName": "Environments", + "safeName": "Environments" } }, - "wireValue": "hasFileDownloadEndpoints" + "wireValue": "environments" }, "valueType": { - "_type": "primitive", - "primitive": "BOOLEAN" + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "EnvironmentsConfig", + "camelCase": { + "unsafeName": "environmentsConfig", + "safeName": "environmentsConfig" + }, + "snakeCase": { + "unsafeName": "environments_config", + "safeName": "environments_config" + }, + "screamingSnakeCase": { + "unsafeName": "ENVIRONMENTS_CONFIG", + "safeName": "ENVIRONMENTS_CONFIG" + }, + "pascalCase": { + "unsafeName": "EnvironmentsConfig", + "safeName": "EnvironmentsConfig" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "environment", + "camelCase": { + "unsafeName": "environment", + "safeName": "environment" + }, + "snakeCase": { + "unsafeName": "environment", + "safeName": "environment" + }, + "screamingSnakeCase": { + "unsafeName": "ENVIRONMENT", + "safeName": "ENVIRONMENT" + }, + "pascalCase": { + "unsafeName": "Environment", + "safeName": "Environment" + } + } + ], + "packagePath": [], + "file": { + "originalName": "environment", + "camelCase": { + "unsafeName": "environment", + "safeName": "environment" + }, + "snakeCase": { + "unsafeName": "environment", + "safeName": "environment" + }, + "screamingSnakeCase": { + "unsafeName": "ENVIRONMENT", + "safeName": "ENVIRONMENT" + }, + "pascalCase": { + "unsafeName": "Environment", + "safeName": "Environment" + } + } + }, + "typeId": "type_environment:EnvironmentsConfig" + } + } }, "availability": null, "docs": null @@ -26042,200 +26824,195 @@ { "name": { "name": { - "originalName": "platformHeaders", + "originalName": "basePath", "camelCase": { - "unsafeName": "platformHeaders", - "safeName": "platformHeaders" + "unsafeName": "basePath", + "safeName": "basePath" }, "snakeCase": { - "unsafeName": "platform_headers", - "safeName": "platform_headers" + "unsafeName": "base_path", + "safeName": "base_path" }, "screamingSnakeCase": { - "unsafeName": "PLATFORM_HEADERS", - "safeName": "PLATFORM_HEADERS" + "unsafeName": "BASE_PATH", + "safeName": "BASE_PATH" }, "pascalCase": { - "unsafeName": "PlatformHeaders", - "safeName": "PlatformHeaders" + "unsafeName": "BasePath", + "safeName": "BasePath" } }, - "wireValue": "platformHeaders" + "wireValue": "basePath" }, "valueType": { - "_type": "named", + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "HttpPath", + "camelCase": { + "unsafeName": "httpPath", + "safeName": "httpPath" + }, + "snakeCase": { + "unsafeName": "http_path", + "safeName": "http_path" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP_PATH", + "safeName": "HTTP_PATH" + }, + "pascalCase": { + "unsafeName": "HttpPath", + "safeName": "HttpPath" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:HttpPath" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { "name": { - "originalName": "PlatformHeaders", + "originalName": "pathParameters", "camelCase": { - "unsafeName": "platformHeaders", - "safeName": "platformHeaders" + "unsafeName": "pathParameters", + "safeName": "pathParameters" }, "snakeCase": { - "unsafeName": "platform_headers", - "safeName": "platform_headers" + "unsafeName": "path_parameters", + "safeName": "path_parameters" }, "screamingSnakeCase": { - "unsafeName": "PLATFORM_HEADERS", - "safeName": "PLATFORM_HEADERS" + "unsafeName": "PATH_PARAMETERS", + "safeName": "PATH_PARAMETERS" }, "pascalCase": { - "unsafeName": "PlatformHeaders", - "safeName": "PlatformHeaders" + "unsafeName": "PathParameters", + "safeName": "PathParameters" } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", + "wireValue": "pathParameters" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "PathParameter", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "pathParameter", + "safeName": "pathParameter" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "path_parameter", + "safeName": "path_parameter" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "PATH_PARAMETER", + "safeName": "PATH_PARAMETER" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "PathParameter", + "safeName": "PathParameter" } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } + "typeId": "type_http:PathParameter" } - }, - "typeId": "type_ir:PlatformHeaders" - }, - "availability": null, - "docs": null - } - ] - }, - "referencedTypes": [ - "type_ir:PlatformHeaders" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_ir:PlatformHeaders": { - "name": { - "name": { - "originalName": "PlatformHeaders", - "camelCase": { - "unsafeName": "platformHeaders", - "safeName": "platformHeaders" - }, - "snakeCase": { - "unsafeName": "platform_headers", - "safeName": "platform_headers" - }, - "screamingSnakeCase": { - "unsafeName": "PLATFORM_HEADERS", - "safeName": "PLATFORM_HEADERS" - }, - "pascalCase": { - "unsafeName": "PlatformHeaders", - "safeName": "PlatformHeaders" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:PlatformHeaders" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "language", - "camelCase": { - "unsafeName": "language", - "safeName": "language" - }, - "snakeCase": { - "unsafeName": "language", - "safeName": "language" - }, - "screamingSnakeCase": { - "unsafeName": "LANGUAGE", - "safeName": "LANGUAGE" - }, - "pascalCase": { - "unsafeName": "Language", - "safeName": "Language" - } - }, - "wireValue": "language" - }, - "valueType": { - "_type": "primitive", - "primitive": "STRING" }, "availability": null, "docs": null @@ -26243,233 +27020,137 @@ { "name": { "name": { - "originalName": "sdkName", + "originalName": "errorDiscriminationStrategy", "camelCase": { - "unsafeName": "sdkName", - "safeName": "sdkName" + "unsafeName": "errorDiscriminationStrategy", + "safeName": "errorDiscriminationStrategy" }, "snakeCase": { - "unsafeName": "sdk_name", - "safeName": "sdk_name" + "unsafeName": "error_discrimination_strategy", + "safeName": "error_discrimination_strategy" }, "screamingSnakeCase": { - "unsafeName": "SDK_NAME", - "safeName": "SDK_NAME" + "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", + "safeName": "ERROR_DISCRIMINATION_STRATEGY" }, "pascalCase": { - "unsafeName": "SdkName", - "safeName": "SdkName" + "unsafeName": "ErrorDiscriminationStrategy", + "safeName": "ErrorDiscriminationStrategy" } }, - "wireValue": "sdkName" + "wireValue": "errorDiscriminationStrategy" }, "valueType": { - "_type": "primitive", - "primitive": "STRING" - }, - "availability": null, - "docs": null - }, - { - "name": { + "_type": "named", "name": { - "originalName": "sdkVersion", + "originalName": "ErrorDiscriminationStrategy", "camelCase": { - "unsafeName": "sdkVersion", - "safeName": "sdkVersion" + "unsafeName": "errorDiscriminationStrategy", + "safeName": "errorDiscriminationStrategy" }, "snakeCase": { - "unsafeName": "sdk_version", - "safeName": "sdk_version" + "unsafeName": "error_discrimination_strategy", + "safeName": "error_discrimination_strategy" }, "screamingSnakeCase": { - "unsafeName": "SDK_VERSION", - "safeName": "SDK_VERSION" + "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", + "safeName": "ERROR_DISCRIMINATION_STRATEGY" }, "pascalCase": { - "unsafeName": "SdkVersion", - "safeName": "SdkVersion" + "unsafeName": "ErrorDiscriminationStrategy", + "safeName": "ErrorDiscriminationStrategy" } }, - "wireValue": "sdkVersion" - }, - "valueType": { - "_type": "primitive", - "primitive": "STRING" - }, - "availability": null, - "docs": null - } - ] - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_ir:ErrorDiscriminationStrategy": { - "name": { - "name": { - "originalName": "ErrorDiscriminationStrategy", - "camelCase": { - "unsafeName": "errorDiscriminationStrategy", - "safeName": "errorDiscriminationStrategy" - }, - "snakeCase": { - "unsafeName": "error_discrimination_strategy", - "safeName": "error_discrimination_strategy" - }, - "screamingSnakeCase": { - "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", - "safeName": "ERROR_DISCRIMINATION_STRATEGY" - }, - "pascalCase": { - "unsafeName": "ErrorDiscriminationStrategy", - "safeName": "ErrorDiscriminationStrategy" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:ErrorDiscriminationStrategy" - }, - "shape": { - "_type": "union", - "discriminant": { - "name": { - "originalName": "type", - "camelCase": { - "unsafeName": "type", - "safeName": "type" - }, - "snakeCase": { - "unsafeName": "type", - "safeName": "type" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" - }, - "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" - } - }, - "wireValue": "type" - }, - "extends": [], - "baseProperties": [], - "types": [ - { - "discriminantValue": { - "name": { - "originalName": "statusCode", - "camelCase": { - "unsafeName": "statusCode", - "safeName": "statusCode" - }, - "snakeCase": { - "unsafeName": "status_code", - "safeName": "status_code" - }, - "screamingSnakeCase": { - "unsafeName": "STATUS_CODE", - "safeName": "STATUS_CODE" - }, - "pascalCase": { - "unsafeName": "StatusCode", - "safeName": "StatusCode" + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } } }, - "wireValue": "statusCode" - }, - "shape": { - "_type": "noProperties" + "typeId": "type_ir:ErrorDiscriminationStrategy" }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "property", + "originalName": "sdkConfig", "camelCase": { - "unsafeName": "property", - "safeName": "property" + "unsafeName": "sdkConfig", + "safeName": "sdkConfig" }, "snakeCase": { - "unsafeName": "property", - "safeName": "property" + "unsafeName": "sdk_config", + "safeName": "sdk_config" }, "screamingSnakeCase": { - "unsafeName": "PROPERTY", - "safeName": "PROPERTY" + "unsafeName": "SDK_CONFIG", + "safeName": "SDK_CONFIG" }, "pascalCase": { - "unsafeName": "Property", - "safeName": "Property" + "unsafeName": "SdkConfig", + "safeName": "SdkConfig" } }, - "wireValue": "property" + "wireValue": "sdkConfig" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "ErrorDiscriminationByPropertyStrategy", + "originalName": "SdkConfig", "camelCase": { - "unsafeName": "errorDiscriminationByPropertyStrategy", - "safeName": "errorDiscriminationByPropertyStrategy" + "unsafeName": "sdkConfig", + "safeName": "sdkConfig" }, "snakeCase": { - "unsafeName": "error_discrimination_by_property_strategy", - "safeName": "error_discrimination_by_property_strategy" + "unsafeName": "sdk_config", + "safeName": "sdk_config" }, "screamingSnakeCase": { - "unsafeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY", - "safeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY" + "unsafeName": "SDK_CONFIG", + "safeName": "SDK_CONFIG" }, "pascalCase": { - "unsafeName": "ErrorDiscriminationByPropertyStrategy", - "safeName": "ErrorDiscriminationByPropertyStrategy" + "unsafeName": "SdkConfig", + "safeName": "SdkConfig" } }, "fernFilepath": { @@ -26515,180 +27196,105 @@ } } }, - "typeId": "type_ir:ErrorDiscriminationByPropertyStrategy" + "typeId": "type_ir:SdkConfig" }, + "availability": null, "docs": null - } - ] - }, - "referencedTypes": [ - "type_ir:ErrorDiscriminationByPropertyStrategy", - "type_commons:NameAndWireValue", - "type_commons:Name", - "type_commons:SafeAndUnsafeString" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_ir:ErrorDiscriminationByPropertyStrategy": { - "name": { - "name": { - "originalName": "ErrorDiscriminationByPropertyStrategy", - "camelCase": { - "unsafeName": "errorDiscriminationByPropertyStrategy", - "safeName": "errorDiscriminationByPropertyStrategy" - }, - "snakeCase": { - "unsafeName": "error_discrimination_by_property_strategy", - "safeName": "error_discrimination_by_property_strategy" }, - "screamingSnakeCase": { - "unsafeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY", - "safeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY" - }, - "pascalCase": { - "unsafeName": "ErrorDiscriminationByPropertyStrategy", - "safeName": "ErrorDiscriminationByPropertyStrategy" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + { + "name": { + "name": { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:ErrorDiscriminationByPropertyStrategy" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "discriminant", - "camelCase": { - "unsafeName": "discriminant", - "safeName": "discriminant" - }, - "snakeCase": { - "unsafeName": "discriminant", - "safeName": "discriminant" - }, - "screamingSnakeCase": { - "unsafeName": "DISCRIMINANT", - "safeName": "DISCRIMINANT" - }, - "pascalCase": { - "unsafeName": "Discriminant", - "safeName": "Discriminant" - } - }, - "wireValue": "discriminant" + "wireValue": "variables" }, "valueType": { - "_type": "named", - "name": { - "originalName": "NameAndWireValue", - "camelCase": { - "unsafeName": "nameAndWireValue", - "safeName": "nameAndWireValue" - }, - "snakeCase": { - "unsafeName": "name_and_wire_value", - "safeName": "name_and_wire_value" - }, - "screamingSnakeCase": { - "unsafeName": "NAME_AND_WIRE_VALUE", - "safeName": "NAME_AND_WIRE_VALUE" - }, - "pascalCase": { - "unsafeName": "NameAndWireValue", - "safeName": "NameAndWireValue" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "VariableDeclaration", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "variableDeclaration", + "safeName": "variableDeclaration" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "variable_declaration", + "safeName": "variable_declaration" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "VARIABLE_DECLARATION", + "safeName": "VARIABLE_DECLARATION" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "VariableDeclaration", + "safeName": "VariableDeclaration" } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "fernFilepath": { + "allParts": [ + { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + ], + "packagePath": [], + "file": { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } + "typeId": "type_variables:VariableDeclaration" } - }, - "typeId": "type_commons:NameAndWireValue" + } }, "availability": null, "docs": null @@ -26696,91 +27302,91 @@ { "name": { "name": { - "originalName": "contentProperty", + "originalName": "serviceTypeReferenceInfo", "camelCase": { - "unsafeName": "contentProperty", - "safeName": "contentProperty" + "unsafeName": "serviceTypeReferenceInfo", + "safeName": "serviceTypeReferenceInfo" }, "snakeCase": { - "unsafeName": "content_property", - "safeName": "content_property" + "unsafeName": "service_type_reference_info", + "safeName": "service_type_reference_info" }, "screamingSnakeCase": { - "unsafeName": "CONTENT_PROPERTY", - "safeName": "CONTENT_PROPERTY" + "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", + "safeName": "SERVICE_TYPE_REFERENCE_INFO" }, "pascalCase": { - "unsafeName": "ContentProperty", - "safeName": "ContentProperty" + "unsafeName": "ServiceTypeReferenceInfo", + "safeName": "ServiceTypeReferenceInfo" } }, - "wireValue": "contentProperty" + "wireValue": "serviceTypeReferenceInfo" }, "valueType": { "_type": "named", "name": { - "originalName": "NameAndWireValue", + "originalName": "ServiceTypeReferenceInfo", "camelCase": { - "unsafeName": "nameAndWireValue", - "safeName": "nameAndWireValue" + "unsafeName": "serviceTypeReferenceInfo", + "safeName": "serviceTypeReferenceInfo" }, "snakeCase": { - "unsafeName": "name_and_wire_value", - "safeName": "name_and_wire_value" + "unsafeName": "service_type_reference_info", + "safeName": "service_type_reference_info" }, "screamingSnakeCase": { - "unsafeName": "NAME_AND_WIRE_VALUE", - "safeName": "NAME_AND_WIRE_VALUE" + "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", + "safeName": "SERVICE_TYPE_REFERENCE_INFO" }, "pascalCase": { - "unsafeName": "NameAndWireValue", - "safeName": "NameAndWireValue" + "unsafeName": "ServiceTypeReferenceInfo", + "safeName": "ServiceTypeReferenceInfo" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "ir", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Ir", + "safeName": "Ir" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "ir", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Ir", + "safeName": "Ir" } } }, - "typeId": "type_commons:NameAndWireValue" + "typeId": "type_ir:ServiceTypeReferenceInfo" }, "availability": null, "docs": null @@ -26788,33 +27394,179 @@ ] }, "referencedTypes": [ - "type_commons:NameAndWireValue", "type_commons:Name", - "type_commons:SafeAndUnsafeString" + "type_commons:SafeAndUnsafeString", + "type_auth:ApiAuth", + "type_commons:WithDocs", + "type_auth:AuthSchemesRequirement", + "type_auth:AuthScheme", + "type_auth:BearerAuthScheme", + "type_auth:EnvironmentVariable", + "type_auth:BasicAuthScheme", + "type_auth:HeaderAuthScheme", + "type_commons:NameAndWireValue", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType", + "type_http:HttpHeader", + "type_commons:Declaration", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_types:TypeDeclaration", + "type_types:Type", + "type_types:AliasTypeDeclaration", + "type_types:ResolvedTypeReference", + "type_types:ResolvedNamedType", + "type_types:ShapeType", + "type_types:EnumTypeDeclaration", + "type_types:EnumValue", + "type_types:ObjectTypeDeclaration", + "type_types:ObjectProperty", + "type_types:UnionTypeDeclaration", + "type_types:SingleUnionType", + "type_types:SingleUnionTypeProperties", + "type_types:SingleUnionTypeProperty", + "type_types:UndiscriminatedUnionTypeDeclaration", + "type_types:UndiscriminatedUnionMember", + "type_types:ExampleType", + "type_commons:WithJsonExample", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleTypeReference", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:ExampleEnumType", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType", + "type_commons:ServiceId", + "type_http:HttpService", + "type_http:DeclaredServiceName", + "type_http:HttpPath", + "type_http:HttpPathPart", + "type_http:HttpEndpoint", + "type_commons:EndpointId", + "type_http:EndpointName", + "type_http:HttpMethod", + "type_environment:EnvironmentBaseUrlId", + "type_http:PathParameter", + "type_http:PathParameterLocation", + "type_variables:VariableId", + "type_http:QueryParameter", + "type_http:HttpRequestBody", + "type_http:InlinedRequestBody", + "type_http:InlinedRequestBodyProperty", + "type_http:HttpRequestBodyReference", + "type_http:FileUploadRequest", + "type_http:FileUploadRequestProperty", + "type_http:FileProperty", + "type_http:BytesRequest", + "type_http:SdkRequest", + "type_http:SdkRequestShape", + "type_http:SdkRequestBodyType", + "type_http:SdkRequestWrapper", + "type_http:HttpResponse", + "type_http:JsonResponse", + "type_http:JsonResponseBody", + "type_http:JsonResponseBodyWithProperty", + "type_http:FileDownloadResponse", + "type_http:TextResponse", + "type_http:StreamingResponse", + "type_http:StreamingResponseChunkType", + "type_http:ResponseErrors", + "type_http:ResponseError", + "type_errors:DeclaredErrorName", + "type_commons:ErrorId", + "type_http:ExampleEndpointCall", + "type_http:ExamplePathParameter", + "type_http:ExampleHeader", + "type_http:ExampleQueryParameter", + "type_http:ExampleRequestBody", + "type_http:ExampleInlinedRequestBody", + "type_http:ExampleInlinedRequestBodyProperty", + "type_http:ExampleResponse", + "type_http:ExampleEndpointSuccessResponse", + "type_http:ExampleEndpointErrorResponse", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage", + "type_commons:WebhookGroupId", + "type_webhooks:WebhookGroup", + "type_webhooks:Webhook", + "type_webhooks:WebhookName", + "type_webhooks:WebhookHttpMethod", + "type_webhooks:WebhookPayload", + "type_webhooks:InlinedWebhookPayload", + "type_webhooks:InlinedWebhookPayloadProperty", + "type_webhooks:WebhookPayloadReference", + "type_commons:WebsocketChannelId", + "type_websocket:WebsocketChannel", + "type_websocket:WebsocketMessageId", + "type_websocket:WebsocketMessage", + "type_websocket:WebsocketMessageOrigin", + "type_websocket:WebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBodyProperty", + "type_websocket:WebsocketMessageBodyReference", + "type_errors:ErrorDeclaration", + "type_commons:SubpackageId", + "type_ir:Subpackage", + "type_ir:Package", + "type_ir:PackageNavigationConfig", + "type_constants:Constants", + "type_environment:EnvironmentsConfig", + "type_environment:EnvironmentId", + "type_environment:Environments", + "type_environment:SingleBaseUrlEnvironments", + "type_environment:SingleBaseUrlEnvironment", + "type_environment:EnvironmentUrl", + "type_environment:MultipleBaseUrlsEnvironments", + "type_environment:EnvironmentBaseUrlWithId", + "type_environment:MultipleBaseUrlsEnvironment", + "type_ir:ErrorDiscriminationStrategy", + "type_ir:ErrorDiscriminationByPropertyStrategy", + "type_ir:SdkConfig", + "type_ir:PlatformHeaders", + "type_variables:VariableDeclaration", + "type_ir:ServiceTypeReferenceInfo" ], "examples": [], "availability": null, - "docs": null + "docs": "Complete representation of the API schema" }, - "type_ir:Package": { + "type_ir:SdkConfig": { "name": { "name": { - "originalName": "Package", + "originalName": "SdkConfig", "camelCase": { - "unsafeName": "package", - "safeName": "package" + "unsafeName": "sdkConfig", + "safeName": "sdkConfig" }, "snakeCase": { - "unsafeName": "package", - "safeName": "package" + "unsafeName": "sdk_config", + "safeName": "sdk_config" }, "screamingSnakeCase": { - "unsafeName": "PACKAGE", - "safeName": "PACKAGE" + "unsafeName": "SDK_CONFIG", + "safeName": "SDK_CONFIG" }, "pascalCase": { - "unsafeName": "Package", - "safeName": "Package" + "unsafeName": "SdkConfig", + "safeName": "SdkConfig" } }, "fernFilepath": { @@ -26860,166 +27612,98 @@ } } }, - "typeId": "type_ir:Package" + "typeId": "type_ir:SdkConfig" }, "shape": { "_type": "object", - "extends": [ + "extends": [], + "properties": [ { "name": { - "originalName": "WithDocs", - "camelCase": { - "unsafeName": "withDocs", - "safeName": "withDocs" - }, - "snakeCase": { - "unsafeName": "with_docs", - "safeName": "with_docs" - }, - "screamingSnakeCase": { - "unsafeName": "WITH_DOCS", - "safeName": "WITH_DOCS" - }, - "pascalCase": { - "unsafeName": "WithDocs", - "safeName": "WithDocs" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", + "name": { + "originalName": "isAuthMandatory", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "isAuthMandatory", + "safeName": "isAuthMandatory" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "is_auth_mandatory", + "safeName": "is_auth_mandatory" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IS_AUTH_MANDATORY", + "safeName": "IS_AUTH_MANDATORY" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "IsAuthMandatory", + "safeName": "IsAuthMandatory" } - } + }, + "wireValue": "isAuthMandatory" }, - "typeId": "type_commons:WithDocs" - } - ], - "properties": [ + "valueType": { + "_type": "primitive", + "primitive": "BOOLEAN" + }, + "availability": null, + "docs": null + }, { "name": { "name": { - "originalName": "fernFilepath", + "originalName": "hasStreamingEndpoints", "camelCase": { - "unsafeName": "fernFilepath", - "safeName": "fernFilepath" + "unsafeName": "hasStreamingEndpoints", + "safeName": "hasStreamingEndpoints" }, "snakeCase": { - "unsafeName": "fern_filepath", - "safeName": "fern_filepath" + "unsafeName": "has_streaming_endpoints", + "safeName": "has_streaming_endpoints" }, "screamingSnakeCase": { - "unsafeName": "FERN_FILEPATH", - "safeName": "FERN_FILEPATH" + "unsafeName": "HAS_STREAMING_ENDPOINTS", + "safeName": "HAS_STREAMING_ENDPOINTS" }, "pascalCase": { - "unsafeName": "FernFilepath", - "safeName": "FernFilepath" + "unsafeName": "HasStreamingEndpoints", + "safeName": "HasStreamingEndpoints" } }, - "wireValue": "fernFilepath" + "wireValue": "hasStreamingEndpoints" }, "valueType": { - "_type": "named", - "name": { - "originalName": "FernFilepath", - "camelCase": { - "unsafeName": "fernFilepath", - "safeName": "fernFilepath" + "_type": "primitive", + "primitive": "BOOLEAN" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "hasFileDownloadEndpoints", + "camelCase": { + "unsafeName": "hasFileDownloadEndpoints", + "safeName": "hasFileDownloadEndpoints" }, "snakeCase": { - "unsafeName": "fern_filepath", - "safeName": "fern_filepath" + "unsafeName": "has_file_download_endpoints", + "safeName": "has_file_download_endpoints" }, "screamingSnakeCase": { - "unsafeName": "FERN_FILEPATH", - "safeName": "FERN_FILEPATH" + "unsafeName": "HAS_FILE_DOWNLOAD_ENDPOINTS", + "safeName": "HAS_FILE_DOWNLOAD_ENDPOINTS" }, "pascalCase": { - "unsafeName": "FernFilepath", - "safeName": "FernFilepath" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } + "unsafeName": "HasFileDownloadEndpoints", + "safeName": "HasFileDownloadEndpoints" } }, - "typeId": "type_commons:FernFilepath" + "wireValue": "hasFileDownloadEndpoints" + }, + "valueType": { + "_type": "primitive", + "primitive": "BOOLEAN" }, "availability": null, "docs": null @@ -27027,195 +27711,200 @@ { "name": { "name": { - "originalName": "service", + "originalName": "platformHeaders", "camelCase": { - "unsafeName": "service", - "safeName": "service" + "unsafeName": "platformHeaders", + "safeName": "platformHeaders" }, "snakeCase": { - "unsafeName": "service", - "safeName": "service" + "unsafeName": "platform_headers", + "safeName": "platform_headers" }, "screamingSnakeCase": { - "unsafeName": "SERVICE", - "safeName": "SERVICE" + "unsafeName": "PLATFORM_HEADERS", + "safeName": "PLATFORM_HEADERS" }, "pascalCase": { - "unsafeName": "Service", - "safeName": "Service" + "unsafeName": "PlatformHeaders", + "safeName": "PlatformHeaders" } }, - "wireValue": "service" + "wireValue": "platformHeaders" }, "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "ServiceId", + "_type": "named", + "name": { + "originalName": "PlatformHeaders", + "camelCase": { + "unsafeName": "platformHeaders", + "safeName": "platformHeaders" + }, + "snakeCase": { + "unsafeName": "platform_headers", + "safeName": "platform_headers" + }, + "screamingSnakeCase": { + "unsafeName": "PLATFORM_HEADERS", + "safeName": "PLATFORM_HEADERS" + }, + "pascalCase": { + "unsafeName": "PlatformHeaders", + "safeName": "PlatformHeaders" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", "camelCase": { - "unsafeName": "serviceId", - "safeName": "serviceId" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "service_id", - "safeName": "service_id" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "SERVICE_ID", - "safeName": "SERVICE_ID" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "ServiceId", - "safeName": "ServiceId" + "unsafeName": "Ir", + "safeName": "Ir" } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" }, - "typeId": "type_commons:ServiceId" + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } } - } + }, + "typeId": "type_ir:PlatformHeaders" }, "availability": null, "docs": null + } + ] + }, + "referencedTypes": [ + "type_ir:PlatformHeaders" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_ir:PlatformHeaders": { + "name": { + "name": { + "originalName": "PlatformHeaders", + "camelCase": { + "unsafeName": "platformHeaders", + "safeName": "platformHeaders" }, + "snakeCase": { + "unsafeName": "platform_headers", + "safeName": "platform_headers" + }, + "screamingSnakeCase": { + "unsafeName": "PLATFORM_HEADERS", + "safeName": "PLATFORM_HEADERS" + }, + "pascalCase": { + "unsafeName": "PlatformHeaders", + "safeName": "PlatformHeaders" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:PlatformHeaders" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ { "name": { "name": { - "originalName": "types", + "originalName": "language", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "language", + "safeName": "language" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "language", + "safeName": "language" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "LANGUAGE", + "safeName": "LANGUAGE" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Language", + "safeName": "Language" } }, - "wireValue": "types" + "wireValue": "language" }, "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "TypeId", - "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" - }, - "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" - }, - "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:TypeId" - } - } + "_type": "primitive", + "primitive": "STRING" }, "availability": null, "docs": null @@ -27223,97 +27912,29 @@ { "name": { "name": { - "originalName": "errors", + "originalName": "sdkName", "camelCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "sdkName", + "safeName": "sdkName" }, "snakeCase": { - "unsafeName": "errors", - "safeName": "errors" + "unsafeName": "sdk_name", + "safeName": "sdk_name" }, "screamingSnakeCase": { - "unsafeName": "ERRORS", - "safeName": "ERRORS" + "unsafeName": "SDK_NAME", + "safeName": "SDK_NAME" }, "pascalCase": { - "unsafeName": "Errors", - "safeName": "Errors" + "unsafeName": "SdkName", + "safeName": "SdkName" } }, - "wireValue": "errors" + "wireValue": "sdkName" }, "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "ErrorId", - "camelCase": { - "unsafeName": "errorId", - "safeName": "errorId" - }, - "snakeCase": { - "unsafeName": "error_id", - "safeName": "error_id" - }, - "screamingSnakeCase": { - "unsafeName": "ERROR_ID", - "safeName": "ERROR_ID" - }, - "pascalCase": { - "unsafeName": "ErrorId", - "safeName": "ErrorId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:ErrorId" - } - } + "_type": "primitive", + "primitive": "STRING" }, "availability": null, "docs": null @@ -27321,364 +27942,59 @@ { "name": { "name": { - "originalName": "webhooks", + "originalName": "sdkVersion", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "sdkVersion", + "safeName": "sdkVersion" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "sdk_version", + "safeName": "sdk_version" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "SDK_VERSION", + "safeName": "SDK_VERSION" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "SdkVersion", + "safeName": "SdkVersion" } }, - "wireValue": "webhooks" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "WebhookGroupId", - "camelCase": { - "unsafeName": "webhookGroupId", - "safeName": "webhookGroupId" - }, - "snakeCase": { - "unsafeName": "webhook_group_id", - "safeName": "webhook_group_id" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOK_GROUP_ID", - "safeName": "WEBHOOK_GROUP_ID" - }, - "pascalCase": { - "unsafeName": "WebhookGroupId", - "safeName": "WebhookGroupId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:WebhookGroupId" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "subpackages", - "camelCase": { - "unsafeName": "subpackages", - "safeName": "subpackages" - }, - "snakeCase": { - "unsafeName": "subpackages", - "safeName": "subpackages" - }, - "screamingSnakeCase": { - "unsafeName": "SUBPACKAGES", - "safeName": "SUBPACKAGES" - }, - "pascalCase": { - "unsafeName": "Subpackages", - "safeName": "Subpackages" - } - }, - "wireValue": "subpackages" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "SubpackageId", - "camelCase": { - "unsafeName": "subpackageId", - "safeName": "subpackageId" - }, - "snakeCase": { - "unsafeName": "subpackage_id", - "safeName": "subpackage_id" - }, - "screamingSnakeCase": { - "unsafeName": "SUBPACKAGE_ID", - "safeName": "SUBPACKAGE_ID" - }, - "pascalCase": { - "unsafeName": "SubpackageId", - "safeName": "SubpackageId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:SubpackageId" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "hasEndpointsInTree", - "camelCase": { - "unsafeName": "hasEndpointsInTree", - "safeName": "hasEndpointsInTree" - }, - "snakeCase": { - "unsafeName": "has_endpoints_in_tree", - "safeName": "has_endpoints_in_tree" - }, - "screamingSnakeCase": { - "unsafeName": "HAS_ENDPOINTS_IN_TREE", - "safeName": "HAS_ENDPOINTS_IN_TREE" - }, - "pascalCase": { - "unsafeName": "HasEndpointsInTree", - "safeName": "HasEndpointsInTree" - } - }, - "wireValue": "hasEndpointsInTree" + "wireValue": "sdkVersion" }, "valueType": { "_type": "primitive", - "primitive": "BOOLEAN" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "navigationConfig", - "camelCase": { - "unsafeName": "navigationConfig", - "safeName": "navigationConfig" - }, - "snakeCase": { - "unsafeName": "navigation_config", - "safeName": "navigation_config" - }, - "screamingSnakeCase": { - "unsafeName": "NAVIGATION_CONFIG", - "safeName": "NAVIGATION_CONFIG" - }, - "pascalCase": { - "unsafeName": "NavigationConfig", - "safeName": "NavigationConfig" - } - }, - "wireValue": "navigationConfig" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "PackageNavigationConfig", - "camelCase": { - "unsafeName": "packageNavigationConfig", - "safeName": "packageNavigationConfig" - }, - "snakeCase": { - "unsafeName": "package_navigation_config", - "safeName": "package_navigation_config" - }, - "screamingSnakeCase": { - "unsafeName": "PACKAGE_NAVIGATION_CONFIG", - "safeName": "PACKAGE_NAVIGATION_CONFIG" - }, - "pascalCase": { - "unsafeName": "PackageNavigationConfig", - "safeName": "PackageNavigationConfig" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - }, - "typeId": "type_ir:PackageNavigationConfig" - } - } + "primitive": "STRING" }, "availability": null, "docs": null } ] }, - "referencedTypes": [ - "type_commons:WithDocs", - "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_commons:ServiceId", - "type_commons:TypeId", - "type_commons:ErrorId", - "type_commons:WebhookGroupId", - "type_commons:SubpackageId", - "type_ir:PackageNavigationConfig" - ], + "referencedTypes": [], "examples": [], "availability": null, "docs": null }, - "type_ir:Subpackage": { + "type_ir:ErrorDiscriminationStrategy": { "name": { "name": { - "originalName": "Subpackage", + "originalName": "ErrorDiscriminationStrategy", "camelCase": { - "unsafeName": "subpackage", - "safeName": "subpackage" + "unsafeName": "errorDiscriminationStrategy", + "safeName": "errorDiscriminationStrategy" }, "snakeCase": { - "unsafeName": "subpackage", - "safeName": "subpackage" + "unsafeName": "error_discrimination_strategy", + "safeName": "error_discrimination_strategy" }, "screamingSnakeCase": { - "unsafeName": "SUBPACKAGE", - "safeName": "SUBPACKAGE" + "unsafeName": "ERROR_DISCRIMINATION_STRATEGY", + "safeName": "ERROR_DISCRIMINATION_STRATEGY" }, "pascalCase": { - "unsafeName": "Subpackage", - "safeName": "Subpackage" + "unsafeName": "ErrorDiscriminationStrategy", + "safeName": "ErrorDiscriminationStrategy" } }, "fernFilepath": { @@ -27724,208 +28040,185 @@ } } }, - "typeId": "type_ir:Subpackage" + "typeId": "type_ir:ErrorDiscriminationStrategy" }, "shape": { - "_type": "object", - "extends": [ - { - "name": { - "originalName": "Package", - "camelCase": { - "unsafeName": "package", - "safeName": "package" - }, - "snakeCase": { - "unsafeName": "package", - "safeName": "package" - }, - "screamingSnakeCase": { - "unsafeName": "PACKAGE", - "safeName": "PACKAGE" - }, - "pascalCase": { - "unsafeName": "Package", - "safeName": "Package" - } + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "ir", - "camelCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" - }, - "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" - }, - "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" - } - } - ], - "packagePath": [], - "file": { - "originalName": "ir", + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "extends": [], + "baseProperties": [], + "types": [ + { + "discriminantValue": { + "name": { + "originalName": "statusCode", "camelCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "statusCode", + "safeName": "statusCode" }, "snakeCase": { - "unsafeName": "ir", - "safeName": "ir" + "unsafeName": "status_code", + "safeName": "status_code" }, "screamingSnakeCase": { - "unsafeName": "IR", - "safeName": "IR" + "unsafeName": "STATUS_CODE", + "safeName": "STATUS_CODE" }, "pascalCase": { - "unsafeName": "Ir", - "safeName": "Ir" + "unsafeName": "StatusCode", + "safeName": "StatusCode" } - } + }, + "wireValue": "statusCode" }, - "typeId": "type_ir:Package" - } - ], - "properties": [ + "shape": { + "_type": "noProperties" + }, + "docs": null + }, { - "name": { + "discriminantValue": { "name": { - "originalName": "name", + "originalName": "property", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "property", + "safeName": "property" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "property", + "safeName": "property" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "PROPERTY", + "safeName": "PROPERTY" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Property", + "safeName": "Property" } }, - "wireValue": "name" + "wireValue": "property" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "Name", + "originalName": "ErrorDiscriminationByPropertyStrategy", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "errorDiscriminationByPropertyStrategy", + "safeName": "errorDiscriminationByPropertyStrategy" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "error_discrimination_by_property_strategy", + "safeName": "error_discrimination_by_property_strategy" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY", + "safeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "ErrorDiscriminationByPropertyStrategy", + "safeName": "ErrorDiscriminationByPropertyStrategy" } }, "fernFilepath": { "allParts": [ { - "originalName": "commons", + "originalName": "ir", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Ir", + "safeName": "Ir" } } ], "packagePath": [], "file": { - "originalName": "commons", + "originalName": "ir", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Ir", + "safeName": "Ir" } } }, - "typeId": "type_commons:Name" + "typeId": "type_ir:ErrorDiscriminationByPropertyStrategy" }, - "availability": null, "docs": null } ] }, "referencedTypes": [ - "type_ir:Package", - "type_commons:WithDocs", - "type_commons:FernFilepath", + "type_ir:ErrorDiscriminationByPropertyStrategy", + "type_commons:NameAndWireValue", "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_commons:ServiceId", - "type_commons:TypeId", - "type_commons:ErrorId", - "type_commons:WebhookGroupId", - "type_commons:SubpackageId", - "type_ir:PackageNavigationConfig" + "type_commons:SafeAndUnsafeString" ], "examples": [], "availability": null, "docs": null }, - "type_ir:PackageNavigationConfig": { + "type_ir:ErrorDiscriminationByPropertyStrategy": { "name": { "name": { - "originalName": "PackageNavigationConfig", + "originalName": "ErrorDiscriminationByPropertyStrategy", "camelCase": { - "unsafeName": "packageNavigationConfig", - "safeName": "packageNavigationConfig" + "unsafeName": "errorDiscriminationByPropertyStrategy", + "safeName": "errorDiscriminationByPropertyStrategy" }, "snakeCase": { - "unsafeName": "package_navigation_config", - "safeName": "package_navigation_config" + "unsafeName": "error_discrimination_by_property_strategy", + "safeName": "error_discrimination_by_property_strategy" }, "screamingSnakeCase": { - "unsafeName": "PACKAGE_NAVIGATION_CONFIG", - "safeName": "PACKAGE_NAVIGATION_CONFIG" + "unsafeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY", + "safeName": "ERROR_DISCRIMINATION_BY_PROPERTY_STRATEGY" }, "pascalCase": { - "unsafeName": "PackageNavigationConfig", - "safeName": "PackageNavigationConfig" + "unsafeName": "ErrorDiscriminationByPropertyStrategy", + "safeName": "ErrorDiscriminationByPropertyStrategy" } }, "fernFilepath": { @@ -27971,7 +28264,7 @@ } } }, - "typeId": "type_ir:PackageNavigationConfig" + "typeId": "type_ir:ErrorDiscriminationByPropertyStrategy" }, "shape": { "_type": "object", @@ -27980,45 +28273,45 @@ { "name": { "name": { - "originalName": "pointsTo", + "originalName": "discriminant", "camelCase": { - "unsafeName": "pointsTo", - "safeName": "pointsTo" + "unsafeName": "discriminant", + "safeName": "discriminant" }, "snakeCase": { - "unsafeName": "points_to", - "safeName": "points_to" + "unsafeName": "discriminant", + "safeName": "discriminant" }, "screamingSnakeCase": { - "unsafeName": "POINTS_TO", - "safeName": "POINTS_TO" + "unsafeName": "DISCRIMINANT", + "safeName": "DISCRIMINANT" }, "pascalCase": { - "unsafeName": "PointsTo", - "safeName": "PointsTo" + "unsafeName": "Discriminant", + "safeName": "Discriminant" } }, - "wireValue": "pointsTo" + "wireValue": "discriminant" }, "valueType": { "_type": "named", "name": { - "originalName": "SubpackageId", + "originalName": "NameAndWireValue", "camelCase": { - "unsafeName": "subpackageId", - "safeName": "subpackageId" + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" }, "snakeCase": { - "unsafeName": "subpackage_id", - "safeName": "subpackage_id" + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" }, "screamingSnakeCase": { - "unsafeName": "SUBPACKAGE_ID", - "safeName": "SUBPACKAGE_ID" + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" }, "pascalCase": { - "unsafeName": "SubpackageId", - "safeName": "SubpackageId" + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" } }, "fernFilepath": { @@ -28064,7 +28357,99 @@ } } }, - "typeId": "type_commons:SubpackageId" + "typeId": "type_commons:NameAndWireValue" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "contentProperty", + "camelCase": { + "unsafeName": "contentProperty", + "safeName": "contentProperty" + }, + "snakeCase": { + "unsafeName": "content_property", + "safeName": "content_property" + }, + "screamingSnakeCase": { + "unsafeName": "CONTENT_PROPERTY", + "safeName": "CONTENT_PROPERTY" + }, + "pascalCase": { + "unsafeName": "ContentProperty", + "safeName": "ContentProperty" + } + }, + "wireValue": "contentProperty" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "NameAndWireValue", + "camelCase": { + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" + }, + "snakeCase": { + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" + }, + "screamingSnakeCase": { + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" + }, + "pascalCase": { + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:NameAndWireValue" }, "availability": null, "docs": null @@ -28072,31 +28457,33 @@ ] }, "referencedTypes": [ - "type_commons:SubpackageId" + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString" ], "examples": [], "availability": null, "docs": null }, - "type_ir:ServiceTypeReferenceInfo": { + "type_ir:Package": { "name": { "name": { - "originalName": "ServiceTypeReferenceInfo", + "originalName": "Package", "camelCase": { - "unsafeName": "serviceTypeReferenceInfo", - "safeName": "serviceTypeReferenceInfo" + "unsafeName": "package", + "safeName": "package" }, "snakeCase": { - "unsafeName": "service_type_reference_info", - "safeName": "service_type_reference_info" + "unsafeName": "package", + "safeName": "package" }, "screamingSnakeCase": { - "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", - "safeName": "SERVICE_TYPE_REFERENCE_INFO" + "unsafeName": "PACKAGE", + "safeName": "PACKAGE" }, "pascalCase": { - "unsafeName": "ServiceTypeReferenceInfo", - "safeName": "ServiceTypeReferenceInfo" + "unsafeName": "Package", + "safeName": "Package" } }, "fernFilepath": { @@ -28142,40 +28529,198 @@ } } }, - "typeId": "type_ir:ServiceTypeReferenceInfo" + "typeId": "type_ir:Package" }, "shape": { "_type": "object", - "extends": [], + "extends": [ + { + "name": { + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" + }, + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithDocs" + } + ], "properties": [ { "name": { "name": { - "originalName": "typesReferencedOnlyByService", + "originalName": "fernFilepath", "camelCase": { - "unsafeName": "typesReferencedOnlyByService", - "safeName": "typesReferencedOnlyByService" + "unsafeName": "fernFilepath", + "safeName": "fernFilepath" }, "snakeCase": { - "unsafeName": "types_referenced_only_by_service", - "safeName": "types_referenced_only_by_service" + "unsafeName": "fern_filepath", + "safeName": "fern_filepath" }, "screamingSnakeCase": { - "unsafeName": "TYPES_REFERENCED_ONLY_BY_SERVICE", - "safeName": "TYPES_REFERENCED_ONLY_BY_SERVICE" + "unsafeName": "FERN_FILEPATH", + "safeName": "FERN_FILEPATH" }, "pascalCase": { - "unsafeName": "TypesReferencedOnlyByService", - "safeName": "TypesReferencedOnlyByService" + "unsafeName": "FernFilepath", + "safeName": "FernFilepath" } }, - "wireValue": "typesReferencedOnlyByService" + "wireValue": "fernFilepath" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "FernFilepath", + "camelCase": { + "unsafeName": "fernFilepath", + "safeName": "fernFilepath" + }, + "snakeCase": { + "unsafeName": "fern_filepath", + "safeName": "fern_filepath" + }, + "screamingSnakeCase": { + "unsafeName": "FERN_FILEPATH", + "safeName": "FERN_FILEPATH" + }, + "pascalCase": { + "unsafeName": "FernFilepath", + "safeName": "FernFilepath" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:FernFilepath" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "service", + "camelCase": { + "unsafeName": "service", + "safeName": "service" + }, + "snakeCase": { + "unsafeName": "service", + "safeName": "service" + }, + "screamingSnakeCase": { + "unsafeName": "SERVICE", + "safeName": "SERVICE" + }, + "pascalCase": { + "unsafeName": "Service", + "safeName": "Service" + } + }, + "wireValue": "service" }, "valueType": { "_type": "container", "container": { - "_type": "map", - "keyType": { + "_type": "optional", + "optional": { "_type": "named", "name": { "originalName": "ServiceId", @@ -28240,106 +28785,34 @@ } }, "typeId": "type_commons:ServiceId" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "TypeId", - "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" - }, - "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" - }, - "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:TypeId" - } - } } } }, "availability": null, - "docs": "Types referenced by exactly one service." + "docs": null }, { "name": { "name": { - "originalName": "sharedTypes", + "originalName": "types", "camelCase": { - "unsafeName": "sharedTypes", - "safeName": "sharedTypes" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "shared_types", - "safeName": "shared_types" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "SHARED_TYPES", - "safeName": "SHARED_TYPES" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "SharedTypes", - "safeName": "SharedTypes" + "unsafeName": "Types", + "safeName": "Types" } }, - "wireValue": "sharedTypes" + "wireValue": "types" }, "valueType": { "_type": "container", @@ -28414,242 +28887,102 @@ } }, "availability": null, - "docs": "Types referenced by either zero or multiple services." - } - ] - }, - "referencedTypes": [ - "type_commons:ServiceId", - "type_commons:TypeId" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_types:TypeDeclaration": { - "name": { - "name": { - "originalName": "TypeDeclaration", - "camelCase": { - "unsafeName": "typeDeclaration", - "safeName": "typeDeclaration" - }, - "snakeCase": { - "unsafeName": "type_declaration", - "safeName": "type_declaration" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_DECLARATION", - "safeName": "TYPE_DECLARATION" + "docs": null }, - "pascalCase": { - "unsafeName": "TypeDeclaration", - "safeName": "TypeDeclaration" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:TypeDeclaration" - }, - "shape": { - "_type": "object", - "extends": [ - { - "name": { - "originalName": "Declaration", - "camelCase": { - "unsafeName": "declaration", - "safeName": "declaration" - }, - "snakeCase": { - "unsafeName": "declaration", - "safeName": "declaration" - }, - "screamingSnakeCase": { - "unsafeName": "DECLARATION", - "safeName": "DECLARATION" - }, - "pascalCase": { - "unsafeName": "Declaration", - "safeName": "Declaration" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:Declaration" - } - ], - "properties": [ { "name": { "name": { - "originalName": "name", + "originalName": "errors", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "errors", + "safeName": "errors" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "errors", + "safeName": "errors" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "ERRORS", + "safeName": "ERRORS" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Errors", + "safeName": "Errors" } }, - "wireValue": "name" + "wireValue": "errors" }, "valueType": { - "_type": "named", - "name": { - "originalName": "DeclaredTypeName", - "camelCase": { - "unsafeName": "declaredTypeName", - "safeName": "declaredTypeName" - }, - "snakeCase": { - "unsafeName": "declared_type_name", - "safeName": "declared_type_name" - }, - "screamingSnakeCase": { - "unsafeName": "DECLARED_TYPE_NAME", - "safeName": "DECLARED_TYPE_NAME" - }, - "pascalCase": { - "unsafeName": "DeclaredTypeName", - "safeName": "DeclaredTypeName" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "ErrorId", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "errorId", + "safeName": "errorId" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "error_id", + "safeName": "error_id" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "ERROR_ID", + "safeName": "ERROR_ID" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "ErrorId", + "safeName": "ErrorId" } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "typeId": "type_commons:ErrorId" } - }, - "typeId": "type_types:DeclaredTypeName" + } }, "availability": null, "docs": null @@ -28657,91 +28990,97 @@ { "name": { "name": { - "originalName": "shape", + "originalName": "webhooks", "camelCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "SHAPE", - "safeName": "SHAPE" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Shape", - "safeName": "Shape" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } }, - "wireValue": "shape" + "wireValue": "webhooks" }, "valueType": { - "_type": "named", - "name": { - "originalName": "Type", - "camelCase": { - "unsafeName": "type", - "safeName": "type" - }, - "snakeCase": { - "unsafeName": "type", - "safeName": "type" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" - }, - "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "WebhookGroupId", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhookGroupId", + "safeName": "webhookGroupId" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhook_group_id", + "safeName": "webhook_group_id" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOK_GROUP_ID", + "safeName": "WEBHOOK_GROUP_ID" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "WebhookGroupId", + "safeName": "WebhookGroupId" } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "typeId": "type_commons:WebhookGroupId" } - }, - "typeId": "type_types:Type" + } }, "availability": null, "docs": null @@ -28749,95 +29088,95 @@ { "name": { "name": { - "originalName": "examples", + "originalName": "websocket", "camelCase": { - "unsafeName": "examples", - "safeName": "examples" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "examples", - "safeName": "examples" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLES", - "safeName": "EXAMPLES" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Examples", - "safeName": "Examples" + "unsafeName": "Websocket", + "safeName": "Websocket" } }, - "wireValue": "examples" + "wireValue": "websocket" }, "valueType": { "_type": "container", "container": { - "_type": "list", - "list": { + "_type": "optional", + "optional": { "_type": "named", "name": { - "originalName": "ExampleType", + "originalName": "WebsocketChannelId", "camelCase": { - "unsafeName": "exampleType", - "safeName": "exampleType" + "unsafeName": "websocketChannelId", + "safeName": "websocketChannelId" }, "snakeCase": { - "unsafeName": "example_type", - "safeName": "example_type" + "unsafeName": "websocket_channel_id", + "safeName": "websocket_channel_id" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE", - "safeName": "EXAMPLE_TYPE" + "unsafeName": "WEBSOCKET_CHANNEL_ID", + "safeName": "WEBSOCKET_CHANNEL_ID" }, "pascalCase": { - "unsafeName": "ExampleType", - "safeName": "ExampleType" + "unsafeName": "WebsocketChannelId", + "safeName": "WebsocketChannelId" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_types:ExampleType" + "typeId": "type_commons:WebsocketChannelId" } } }, @@ -28847,49 +29186,49 @@ { "name": { "name": { - "originalName": "referencedTypes", + "originalName": "subpackages", "camelCase": { - "unsafeName": "referencedTypes", - "safeName": "referencedTypes" + "unsafeName": "subpackages", + "safeName": "subpackages" }, "snakeCase": { - "unsafeName": "referenced_types", - "safeName": "referenced_types" + "unsafeName": "subpackages", + "safeName": "subpackages" }, "screamingSnakeCase": { - "unsafeName": "REFERENCED_TYPES", - "safeName": "REFERENCED_TYPES" + "unsafeName": "SUBPACKAGES", + "safeName": "SUBPACKAGES" }, "pascalCase": { - "unsafeName": "ReferencedTypes", - "safeName": "ReferencedTypes" + "unsafeName": "Subpackages", + "safeName": "Subpackages" } }, - "wireValue": "referencedTypes" + "wireValue": "subpackages" }, "valueType": { "_type": "container", "container": { - "_type": "set", - "set": { + "_type": "list", + "list": { "_type": "named", "name": { - "originalName": "TypeId", + "originalName": "SubpackageId", "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" + "unsafeName": "subpackageId", + "safeName": "subpackageId" }, "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" + "unsafeName": "subpackage_id", + "safeName": "subpackage_id" }, "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" + "unsafeName": "SUBPACKAGE_ID", + "safeName": "SUBPACKAGE_ID" }, "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" + "unsafeName": "SubpackageId", + "safeName": "SubpackageId" } }, "fernFilepath": { @@ -28935,274 +29274,338 @@ } } }, - "typeId": "type_commons:TypeId" + "typeId": "type_commons:SubpackageId" } } }, "availability": null, - "docs": "All other named types that this type references (directly or indirectly)" - } - ] - }, - "referencedTypes": [ - "type_commons:Declaration", - "type_commons:WithDocs", - "type_commons:Availability", - "type_commons:AvailabilityStatus", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_types:Type", - "type_types:AliasTypeDeclaration", - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", - "type_types:PrimitiveType", - "type_types:ResolvedTypeReference", - "type_types:ResolvedNamedType", - "type_types:ShapeType", - "type_types:EnumTypeDeclaration", - "type_types:EnumValue", - "type_commons:NameAndWireValue", - "type_types:ObjectTypeDeclaration", - "type_types:ObjectProperty", - "type_types:UnionTypeDeclaration", - "type_types:SingleUnionType", - "type_types:SingleUnionTypeProperties", - "type_types:SingleUnionTypeProperty", - "type_types:UndiscriminatedUnionTypeDeclaration", - "type_types:UndiscriminatedUnionMember", - "type_types:ExampleType", - "type_commons:WithJsonExample", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleTypeReference", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:ExampleEnumType", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "docs": null + }, + { + "name": { + "name": { + "originalName": "hasEndpointsInTree", + "camelCase": { + "unsafeName": "hasEndpointsInTree", + "safeName": "hasEndpointsInTree" + }, + "snakeCase": { + "unsafeName": "has_endpoints_in_tree", + "safeName": "has_endpoints_in_tree" + }, + "screamingSnakeCase": { + "unsafeName": "HAS_ENDPOINTS_IN_TREE", + "safeName": "HAS_ENDPOINTS_IN_TREE" + }, + "pascalCase": { + "unsafeName": "HasEndpointsInTree", + "safeName": "HasEndpointsInTree" + } + }, + "wireValue": "hasEndpointsInTree" + }, + "valueType": { + "_type": "primitive", + "primitive": "BOOLEAN" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "navigationConfig", + "camelCase": { + "unsafeName": "navigationConfig", + "safeName": "navigationConfig" + }, + "snakeCase": { + "unsafeName": "navigation_config", + "safeName": "navigation_config" + }, + "screamingSnakeCase": { + "unsafeName": "NAVIGATION_CONFIG", + "safeName": "NAVIGATION_CONFIG" + }, + "pascalCase": { + "unsafeName": "NavigationConfig", + "safeName": "NavigationConfig" + } + }, + "wireValue": "navigationConfig" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "PackageNavigationConfig", + "camelCase": { + "unsafeName": "packageNavigationConfig", + "safeName": "packageNavigationConfig" + }, + "snakeCase": { + "unsafeName": "package_navigation_config", + "safeName": "package_navigation_config" + }, + "screamingSnakeCase": { + "unsafeName": "PACKAGE_NAVIGATION_CONFIG", + "safeName": "PACKAGE_NAVIGATION_CONFIG" + }, + "pascalCase": { + "unsafeName": "PackageNavigationConfig", + "safeName": "PackageNavigationConfig" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:PackageNavigationConfig" + } + } + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_commons:WithDocs", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_commons:ServiceId", + "type_commons:TypeId", + "type_commons:ErrorId", + "type_commons:WebhookGroupId", + "type_commons:WebsocketChannelId", + "type_commons:SubpackageId", + "type_ir:PackageNavigationConfig" ], "examples": [], "availability": null, - "docs": "A type, which is a name and a shape" + "docs": null }, - "type_types:DeclaredTypeName": { + "type_ir:Subpackage": { "name": { "name": { - "originalName": "DeclaredTypeName", + "originalName": "Subpackage", "camelCase": { - "unsafeName": "declaredTypeName", - "safeName": "declaredTypeName" + "unsafeName": "subpackage", + "safeName": "subpackage" }, "snakeCase": { - "unsafeName": "declared_type_name", - "safeName": "declared_type_name" + "unsafeName": "subpackage", + "safeName": "subpackage" }, "screamingSnakeCase": { - "unsafeName": "DECLARED_TYPE_NAME", - "safeName": "DECLARED_TYPE_NAME" + "unsafeName": "SUBPACKAGE", + "safeName": "SUBPACKAGE" }, "pascalCase": { - "unsafeName": "DeclaredTypeName", - "safeName": "DeclaredTypeName" + "unsafeName": "Subpackage", + "safeName": "Subpackage" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "ir", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Ir", + "safeName": "Ir" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "ir", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Ir", + "safeName": "Ir" } } }, - "typeId": "type_types:DeclaredTypeName" + "typeId": "type_ir:Subpackage" }, "shape": { "_type": "object", - "extends": [], - "properties": [ + "extends": [ { "name": { - "name": { - "originalName": "typeId", - "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" - }, - "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" - }, - "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" - } + "originalName": "Package", + "camelCase": { + "unsafeName": "package", + "safeName": "package" }, - "wireValue": "typeId" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "TypeId", - "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" - }, - "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" - }, - "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" - } + "snakeCase": { + "unsafeName": "package", + "safeName": "package" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", + "screamingSnakeCase": { + "unsafeName": "PACKAGE", + "safeName": "PACKAGE" + }, + "pascalCase": { + "unsafeName": "Package", + "safeName": "Package" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "ir", + "safeName": "ir" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "IR", + "safeName": "IR" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Ir", + "safeName": "Ir" } } - }, - "typeId": "type_commons:TypeId" + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } }, - "availability": null, - "docs": null - }, + "typeId": "type_ir:Package" + } + ], + "properties": [ { "name": { "name": { - "originalName": "fernFilepath", + "originalName": "name", "camelCase": { - "unsafeName": "fernFilepath", - "safeName": "fernFilepath" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "fern_filepath", - "safeName": "fern_filepath" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "FERN_FILEPATH", - "safeName": "FERN_FILEPATH" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "FernFilepath", - "safeName": "FernFilepath" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "fernFilepath" + "wireValue": "name" }, "valueType": { "_type": "named", "name": { - "originalName": "FernFilepath", + "originalName": "Name", "camelCase": { - "unsafeName": "fernFilepath", - "safeName": "fernFilepath" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "fern_filepath", - "safeName": "fern_filepath" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "FERN_FILEPATH", - "safeName": "FERN_FILEPATH" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "FernFilepath", - "safeName": "FernFilepath" + "unsafeName": "Name", + "safeName": "Name" } }, "fernFilepath": { @@ -29248,53 +29651,143 @@ } } }, - "typeId": "type_commons:FernFilepath" + "typeId": "type_commons:Name" }, "availability": null, "docs": null + } + ] + }, + "referencedTypes": [ + "type_ir:Package", + "type_commons:WithDocs", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_commons:ServiceId", + "type_commons:TypeId", + "type_commons:ErrorId", + "type_commons:WebhookGroupId", + "type_commons:WebsocketChannelId", + "type_commons:SubpackageId", + "type_ir:PackageNavigationConfig" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_ir:PackageNavigationConfig": { + "name": { + "name": { + "originalName": "PackageNavigationConfig", + "camelCase": { + "unsafeName": "packageNavigationConfig", + "safeName": "packageNavigationConfig" + }, + "snakeCase": { + "unsafeName": "package_navigation_config", + "safeName": "package_navigation_config" }, + "screamingSnakeCase": { + "unsafeName": "PACKAGE_NAVIGATION_CONFIG", + "safeName": "PACKAGE_NAVIGATION_CONFIG" + }, + "pascalCase": { + "unsafeName": "PackageNavigationConfig", + "safeName": "PackageNavigationConfig" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:PackageNavigationConfig" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ { "name": { "name": { - "originalName": "name", + "originalName": "pointsTo", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "pointsTo", + "safeName": "pointsTo" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "points_to", + "safeName": "points_to" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "POINTS_TO", + "safeName": "POINTS_TO" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "PointsTo", + "safeName": "PointsTo" } }, - "wireValue": "name" + "wireValue": "pointsTo" }, "valueType": { "_type": "named", "name": { - "originalName": "Name", + "originalName": "SubpackageId", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "subpackageId", + "safeName": "subpackageId" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "subpackage_id", + "safeName": "subpackage_id" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "SUBPACKAGE_ID", + "safeName": "SUBPACKAGE_ID" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "SubpackageId", + "safeName": "SubpackageId" } }, "fernFilepath": { @@ -29340,7 +29833,7 @@ } } }, - "typeId": "type_commons:Name" + "typeId": "type_commons:SubpackageId" }, "availability": null, "docs": null @@ -29348,34 +29841,379 @@ ] }, "referencedTypes": [ - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString" + "type_commons:SubpackageId" ], "examples": [], "availability": null, "docs": null }, - "type_types:Type": { + "type_ir:ServiceTypeReferenceInfo": { "name": { "name": { - "originalName": "Type", + "originalName": "ServiceTypeReferenceInfo", "camelCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "serviceTypeReferenceInfo", + "safeName": "serviceTypeReferenceInfo" }, "snakeCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "service_type_reference_info", + "safeName": "service_type_reference_info" }, "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" + "unsafeName": "SERVICE_TYPE_REFERENCE_INFO", + "safeName": "SERVICE_TYPE_REFERENCE_INFO" }, "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" + "unsafeName": "ServiceTypeReferenceInfo", + "safeName": "ServiceTypeReferenceInfo" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + ], + "packagePath": [], + "file": { + "originalName": "ir", + "camelCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "snakeCase": { + "unsafeName": "ir", + "safeName": "ir" + }, + "screamingSnakeCase": { + "unsafeName": "IR", + "safeName": "IR" + }, + "pascalCase": { + "unsafeName": "Ir", + "safeName": "Ir" + } + } + }, + "typeId": "type_ir:ServiceTypeReferenceInfo" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "typesReferencedOnlyByService", + "camelCase": { + "unsafeName": "typesReferencedOnlyByService", + "safeName": "typesReferencedOnlyByService" + }, + "snakeCase": { + "unsafeName": "types_referenced_only_by_service", + "safeName": "types_referenced_only_by_service" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES_REFERENCED_ONLY_BY_SERVICE", + "safeName": "TYPES_REFERENCED_ONLY_BY_SERVICE" + }, + "pascalCase": { + "unsafeName": "TypesReferencedOnlyByService", + "safeName": "TypesReferencedOnlyByService" + } + }, + "wireValue": "typesReferencedOnlyByService" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "named", + "name": { + "originalName": "ServiceId", + "camelCase": { + "unsafeName": "serviceId", + "safeName": "serviceId" + }, + "snakeCase": { + "unsafeName": "service_id", + "safeName": "service_id" + }, + "screamingSnakeCase": { + "unsafeName": "SERVICE_ID", + "safeName": "SERVICE_ID" + }, + "pascalCase": { + "unsafeName": "ServiceId", + "safeName": "ServiceId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:ServiceId" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "TypeId", + "camelCase": { + "unsafeName": "typeId", + "safeName": "typeId" + }, + "snakeCase": { + "unsafeName": "type_id", + "safeName": "type_id" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" + }, + "pascalCase": { + "unsafeName": "TypeId", + "safeName": "TypeId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:TypeId" + } + } + } + } + }, + "availability": null, + "docs": "Types referenced by exactly one service." + }, + { + "name": { + "name": { + "originalName": "sharedTypes", + "camelCase": { + "unsafeName": "sharedTypes", + "safeName": "sharedTypes" + }, + "snakeCase": { + "unsafeName": "shared_types", + "safeName": "shared_types" + }, + "screamingSnakeCase": { + "unsafeName": "SHARED_TYPES", + "safeName": "SHARED_TYPES" + }, + "pascalCase": { + "unsafeName": "SharedTypes", + "safeName": "SharedTypes" + } + }, + "wireValue": "sharedTypes" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "TypeId", + "camelCase": { + "unsafeName": "typeId", + "safeName": "typeId" + }, + "snakeCase": { + "unsafeName": "type_id", + "safeName": "type_id" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" + }, + "pascalCase": { + "unsafeName": "TypeId", + "safeName": "TypeId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:TypeId" + } + } + }, + "availability": null, + "docs": "Types referenced by either zero or multiple services." + } + ] + }, + "referencedTypes": [ + "type_commons:ServiceId", + "type_commons:TypeId" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:TypeDeclaration": { + "name": { + "name": { + "originalName": "TypeDeclaration", + "camelCase": { + "unsafeName": "typeDeclaration", + "safeName": "typeDeclaration" + }, + "snakeCase": { + "unsafeName": "type_declaration", + "safeName": "type_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_DECLARATION", + "safeName": "TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "TypeDeclaration", + "safeName": "TypeDeclaration" } }, "fernFilepath": { @@ -29421,77 +30259,120 @@ } } }, - "typeId": "type_types:Type" + "typeId": "type_types:TypeDeclaration" }, "shape": { - "_type": "union", - "discriminant": { - "name": { - "originalName": "type", - "camelCase": { - "unsafeName": "type", - "safeName": "type" - }, - "snakeCase": { - "unsafeName": "type", - "safeName": "type" + "_type": "object", + "extends": [ + { + "name": { + "originalName": "Declaration", + "camelCase": { + "unsafeName": "declaration", + "safeName": "declaration" + }, + "snakeCase": { + "unsafeName": "declaration", + "safeName": "declaration" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARATION", + "safeName": "DECLARATION" + }, + "pascalCase": { + "unsafeName": "Declaration", + "safeName": "Declaration" + } }, - "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" - } - }, - "wireValue": "_type" - }, - "extends": [], - "baseProperties": [], - "types": [ + "typeId": "type_commons:Declaration" + } + ], + "properties": [ { - "discriminantValue": { + "name": { "name": { - "originalName": "alias", + "originalName": "name", "camelCase": { - "unsafeName": "alias", - "safeName": "alias" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "alias", - "safeName": "alias" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "ALIAS", - "safeName": "ALIAS" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Alias", - "safeName": "Alias" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "alias" + "wireValue": "name" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "AliasTypeDeclaration", + "originalName": "DeclaredTypeName", "camelCase": { - "unsafeName": "aliasTypeDeclaration", - "safeName": "aliasTypeDeclaration" + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" }, "snakeCase": { - "unsafeName": "alias_type_declaration", - "safeName": "alias_type_declaration" + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" }, "screamingSnakeCase": { - "unsafeName": "ALIAS_TYPE_DECLARATION", - "safeName": "ALIAS_TYPE_DECLARATION" + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" }, "pascalCase": { - "unsafeName": "AliasTypeDeclaration", - "safeName": "AliasTypeDeclaration" + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" } }, "fernFilepath": { @@ -29537,52 +30418,53 @@ } } }, - "typeId": "type_types:AliasTypeDeclaration" + "typeId": "type_types:DeclaredTypeName" }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "enum", + "originalName": "shape", "camelCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "shape", + "safeName": "shape" }, "snakeCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "shape", + "safeName": "shape" }, "screamingSnakeCase": { - "unsafeName": "ENUM", - "safeName": "ENUM" + "unsafeName": "SHAPE", + "safeName": "SHAPE" }, "pascalCase": { - "unsafeName": "Enum", - "safeName": "Enum" + "unsafeName": "Shape", + "safeName": "Shape" } }, - "wireValue": "enum" + "wireValue": "shape" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "EnumTypeDeclaration", + "originalName": "Type", "camelCase": { - "unsafeName": "enumTypeDeclaration", - "safeName": "enumTypeDeclaration" + "unsafeName": "type", + "safeName": "type" }, "snakeCase": { - "unsafeName": "enum_type_declaration", - "safeName": "enum_type_declaration" + "unsafeName": "type", + "safeName": "type" }, "screamingSnakeCase": { - "unsafeName": "ENUM_TYPE_DECLARATION", - "safeName": "ENUM_TYPE_DECLARATION" + "unsafeName": "TYPE", + "safeName": "TYPE" }, "pascalCase": { - "unsafeName": "EnumTypeDeclaration", - "safeName": "EnumTypeDeclaration" + "unsafeName": "Type", + "safeName": "Type" } }, "fernFilepath": { @@ -29628,306 +30510,231 @@ } } }, - "typeId": "type_types:EnumTypeDeclaration" + "typeId": "type_types:Type" }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "object", + "originalName": "examples", "camelCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "examples", + "safeName": "examples" }, "snakeCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "examples", + "safeName": "examples" }, "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" + "unsafeName": "EXAMPLES", + "safeName": "EXAMPLES" }, "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" + "unsafeName": "Examples", + "safeName": "Examples" } }, - "wireValue": "object" + "wireValue": "examples" }, - "shape": { - "_type": "samePropertiesAsObject", - "name": { - "originalName": "ObjectTypeDeclaration", - "camelCase": { - "unsafeName": "objectTypeDeclaration", - "safeName": "objectTypeDeclaration" - }, - "snakeCase": { - "unsafeName": "object_type_declaration", - "safeName": "object_type_declaration" - }, - "screamingSnakeCase": { - "unsafeName": "OBJECT_TYPE_DECLARATION", - "safeName": "OBJECT_TYPE_DECLARATION" - }, - "pascalCase": { - "unsafeName": "ObjectTypeDeclaration", - "safeName": "ObjectTypeDeclaration" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "ExampleType", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "exampleType", + "safeName": "exampleType" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "example_type", + "safeName": "example_type" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "EXAMPLE_TYPE", + "safeName": "EXAMPLE_TYPE" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "ExampleType", + "safeName": "ExampleType" } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:ObjectTypeDeclaration" - }, - "docs": null - }, - { - "discriminantValue": { - "name": { - "originalName": "union", - "camelCase": { - "unsafeName": "union", - "safeName": "union" - }, - "snakeCase": { - "unsafeName": "union", - "safeName": "union" - }, - "screamingSnakeCase": { - "unsafeName": "UNION", - "safeName": "UNION" - }, - "pascalCase": { - "unsafeName": "Union", - "safeName": "Union" - } - }, - "wireValue": "union" - }, - "shape": { - "_type": "samePropertiesAsObject", - "name": { - "originalName": "UnionTypeDeclaration", - "camelCase": { - "unsafeName": "unionTypeDeclaration", - "safeName": "unionTypeDeclaration" - }, - "snakeCase": { - "unsafeName": "union_type_declaration", - "safeName": "union_type_declaration" - }, - "screamingSnakeCase": { - "unsafeName": "UNION_TYPE_DECLARATION", - "safeName": "UNION_TYPE_DECLARATION" - }, - "pascalCase": { - "unsafeName": "UnionTypeDeclaration", - "safeName": "UnionTypeDeclaration" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "typeId": "type_types:ExampleType" } - }, - "typeId": "type_types:UnionTypeDeclaration" + } }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "undiscriminatedUnion", + "originalName": "referencedTypes", "camelCase": { - "unsafeName": "undiscriminatedUnion", - "safeName": "undiscriminatedUnion" + "unsafeName": "referencedTypes", + "safeName": "referencedTypes" }, "snakeCase": { - "unsafeName": "undiscriminated_union", - "safeName": "undiscriminated_union" + "unsafeName": "referenced_types", + "safeName": "referenced_types" }, "screamingSnakeCase": { - "unsafeName": "UNDISCRIMINATED_UNION", - "safeName": "UNDISCRIMINATED_UNION" + "unsafeName": "REFERENCED_TYPES", + "safeName": "REFERENCED_TYPES" }, "pascalCase": { - "unsafeName": "UndiscriminatedUnion", - "safeName": "UndiscriminatedUnion" + "unsafeName": "ReferencedTypes", + "safeName": "ReferencedTypes" } }, - "wireValue": "undiscriminatedUnion" + "wireValue": "referencedTypes" }, - "shape": { - "_type": "samePropertiesAsObject", - "name": { - "originalName": "UndiscriminatedUnionTypeDeclaration", - "camelCase": { - "unsafeName": "undiscriminatedUnionTypeDeclaration", - "safeName": "undiscriminatedUnionTypeDeclaration" - }, - "snakeCase": { - "unsafeName": "undiscriminated_union_type_declaration", - "safeName": "undiscriminated_union_type_declaration" - }, - "screamingSnakeCase": { - "unsafeName": "UNDISCRIMINATED_UNION_TYPE_DECLARATION", - "safeName": "UNDISCRIMINATED_UNION_TYPE_DECLARATION" - }, - "pascalCase": { - "unsafeName": "UndiscriminatedUnionTypeDeclaration", - "safeName": "UndiscriminatedUnionTypeDeclaration" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", + "valueType": { + "_type": "container", + "container": { + "_type": "set", + "set": { + "_type": "named", + "name": { + "originalName": "TypeId", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "typeId", + "safeName": "typeId" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "type_id", + "safeName": "type_id" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "TypeId", + "safeName": "TypeId" } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "typeId": "type_commons:TypeId" } - }, - "typeId": "type_types:UndiscriminatedUnionTypeDeclaration" + } }, - "docs": null + "availability": null, + "docs": "All other named types that this type references (directly or indirectly)" } ] }, "referencedTypes": [ - "type_types:AliasTypeDeclaration", - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", "type_commons:Name", "type_commons:SafeAndUnsafeString", + "type_types:Type", + "type_types:AliasTypeDeclaration", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", "type_types:PrimitiveType", "type_types:ResolvedTypeReference", "type_types:ResolvedNamedType", "type_types:ShapeType", "type_types:EnumTypeDeclaration", "type_types:EnumValue", - "type_commons:Declaration", - "type_commons:WithDocs", - "type_commons:Availability", - "type_commons:AvailabilityStatus", "type_commons:NameAndWireValue", "type_types:ObjectTypeDeclaration", "type_types:ObjectProperty", @@ -29936,31 +30743,50 @@ "type_types:SingleUnionTypeProperties", "type_types:SingleUnionTypeProperty", "type_types:UndiscriminatedUnionTypeDeclaration", - "type_types:UndiscriminatedUnionMember" + "type_types:UndiscriminatedUnionMember", + "type_types:ExampleType", + "type_commons:WithJsonExample", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleTypeReference", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:ExampleEnumType", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" ], "examples": [], "availability": null, - "docs": null + "docs": "A type, which is a name and a shape" }, - "type_types:AliasTypeDeclaration": { + "type_types:DeclaredTypeName": { "name": { "name": { - "originalName": "AliasTypeDeclaration", + "originalName": "DeclaredTypeName", "camelCase": { - "unsafeName": "aliasTypeDeclaration", - "safeName": "aliasTypeDeclaration" + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" }, "snakeCase": { - "unsafeName": "alias_type_declaration", - "safeName": "alias_type_declaration" + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" }, "screamingSnakeCase": { - "unsafeName": "ALIAS_TYPE_DECLARATION", - "safeName": "ALIAS_TYPE_DECLARATION" + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" }, "pascalCase": { - "unsafeName": "AliasTypeDeclaration", - "safeName": "AliasTypeDeclaration" + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" } }, "fernFilepath": { @@ -30006,7 +30832,7 @@ } } }, - "typeId": "type_types:AliasTypeDeclaration" + "typeId": "type_types:DeclaredTypeName" }, "shape": { "_type": "object", @@ -30015,91 +30841,91 @@ { "name": { "name": { - "originalName": "aliasOf", + "originalName": "typeId", "camelCase": { - "unsafeName": "aliasOf", - "safeName": "aliasOf" + "unsafeName": "typeId", + "safeName": "typeId" }, "snakeCase": { - "unsafeName": "alias_of", - "safeName": "alias_of" + "unsafeName": "type_id", + "safeName": "type_id" }, "screamingSnakeCase": { - "unsafeName": "ALIAS_OF", - "safeName": "ALIAS_OF" + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" }, "pascalCase": { - "unsafeName": "AliasOf", - "safeName": "AliasOf" + "unsafeName": "TypeId", + "safeName": "TypeId" } }, - "wireValue": "aliasOf" + "wireValue": "typeId" }, "valueType": { "_type": "named", "name": { - "originalName": "TypeReference", + "originalName": "TypeId", "camelCase": { - "unsafeName": "typeReference", - "safeName": "typeReference" + "unsafeName": "typeId", + "safeName": "typeId" }, "snakeCase": { - "unsafeName": "type_reference", - "safeName": "type_reference" + "unsafeName": "type_id", + "safeName": "type_id" }, "screamingSnakeCase": { - "unsafeName": "TYPE_REFERENCE", - "safeName": "TYPE_REFERENCE" + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" }, "pascalCase": { - "unsafeName": "TypeReference", - "safeName": "TypeReference" + "unsafeName": "TypeId", + "safeName": "TypeId" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_types:TypeReference" + "typeId": "type_commons:TypeId" }, "availability": null, "docs": null @@ -30107,91 +30933,183 @@ { "name": { "name": { - "originalName": "resolvedType", + "originalName": "fernFilepath", "camelCase": { - "unsafeName": "resolvedType", - "safeName": "resolvedType" + "unsafeName": "fernFilepath", + "safeName": "fernFilepath" }, "snakeCase": { - "unsafeName": "resolved_type", - "safeName": "resolved_type" + "unsafeName": "fern_filepath", + "safeName": "fern_filepath" }, "screamingSnakeCase": { - "unsafeName": "RESOLVED_TYPE", - "safeName": "RESOLVED_TYPE" + "unsafeName": "FERN_FILEPATH", + "safeName": "FERN_FILEPATH" }, "pascalCase": { - "unsafeName": "ResolvedType", - "safeName": "ResolvedType" + "unsafeName": "FernFilepath", + "safeName": "FernFilepath" } }, - "wireValue": "resolvedType" + "wireValue": "fernFilepath" }, "valueType": { "_type": "named", "name": { - "originalName": "ResolvedTypeReference", + "originalName": "FernFilepath", "camelCase": { - "unsafeName": "resolvedTypeReference", - "safeName": "resolvedTypeReference" + "unsafeName": "fernFilepath", + "safeName": "fernFilepath" }, "snakeCase": { - "unsafeName": "resolved_type_reference", - "safeName": "resolved_type_reference" + "unsafeName": "fern_filepath", + "safeName": "fern_filepath" }, "screamingSnakeCase": { - "unsafeName": "RESOLVED_TYPE_REFERENCE", - "safeName": "RESOLVED_TYPE_REFERENCE" + "unsafeName": "FERN_FILEPATH", + "safeName": "FERN_FILEPATH" }, "pascalCase": { - "unsafeName": "ResolvedTypeReference", - "safeName": "ResolvedTypeReference" + "unsafeName": "FernFilepath", + "safeName": "FernFilepath" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } }, - "typeId": "type_types:ResolvedTypeReference" + "typeId": "type_commons:FernFilepath" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Name" }, "availability": null, "docs": null @@ -30199,43 +31117,34 @@ ] }, "referencedTypes": [ - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", - "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_types:PrimitiveType", - "type_types:ResolvedTypeReference", - "type_types:ResolvedNamedType", - "type_types:ShapeType" + "type_commons:SafeAndUnsafeString" ], "examples": [], "availability": null, "docs": null }, - "type_types:ResolvedTypeReference": { + "type_types:Type": { "name": { "name": { - "originalName": "ResolvedTypeReference", + "originalName": "Type", "camelCase": { - "unsafeName": "resolvedTypeReference", - "safeName": "resolvedTypeReference" + "unsafeName": "type", + "safeName": "type" }, "snakeCase": { - "unsafeName": "resolved_type_reference", - "safeName": "resolved_type_reference" + "unsafeName": "type", + "safeName": "type" }, "screamingSnakeCase": { - "unsafeName": "RESOLVED_TYPE_REFERENCE", - "safeName": "RESOLVED_TYPE_REFERENCE" + "unsafeName": "TYPE", + "safeName": "TYPE" }, "pascalCase": { - "unsafeName": "ResolvedTypeReference", - "safeName": "ResolvedTypeReference" + "unsafeName": "Type", + "safeName": "Type" } }, "fernFilepath": { @@ -30281,7 +31190,7 @@ } } }, - "typeId": "type_types:ResolvedTypeReference" + "typeId": "type_types:Type" }, "shape": { "_type": "union", @@ -30313,95 +31222,50 @@ { "discriminantValue": { "name": { - "originalName": "container", + "originalName": "alias", "camelCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "alias", + "safeName": "alias" }, "snakeCase": { - "unsafeName": "container", - "safeName": "container" + "unsafeName": "alias", + "safeName": "alias" }, "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" + "unsafeName": "ALIAS", + "safeName": "ALIAS" }, "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" + "unsafeName": "Alias", + "safeName": "Alias" } }, - "wireValue": "container" + "wireValue": "alias" }, "shape": { - "_type": "singleProperty", + "_type": "samePropertiesAsObject", "name": { - "name": { - "originalName": "container", - "camelCase": { - "unsafeName": "container", - "safeName": "container" - }, - "snakeCase": { - "unsafeName": "container", - "safeName": "container" - }, - "screamingSnakeCase": { - "unsafeName": "CONTAINER", - "safeName": "CONTAINER" - }, - "pascalCase": { - "unsafeName": "Container", - "safeName": "Container" - } + "originalName": "AliasTypeDeclaration", + "camelCase": { + "unsafeName": "aliasTypeDeclaration", + "safeName": "aliasTypeDeclaration" }, - "wireValue": "container" - }, - "type": { - "_type": "named", - "name": { - "originalName": "ContainerType", - "camelCase": { - "unsafeName": "containerType", - "safeName": "containerType" - }, - "snakeCase": { - "unsafeName": "container_type", - "safeName": "container_type" - }, - "screamingSnakeCase": { - "unsafeName": "CONTAINER_TYPE", - "safeName": "CONTAINER_TYPE" - }, - "pascalCase": { - "unsafeName": "ContainerType", - "safeName": "ContainerType" - } + "snakeCase": { + "unsafeName": "alias_type_declaration", + "safeName": "alias_type_declaration" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { + "screamingSnakeCase": { + "unsafeName": "ALIAS_TYPE_DECLARATION", + "safeName": "ALIAS_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "AliasTypeDeclaration", + "safeName": "AliasTypeDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { "originalName": "types", "camelCase": { "unsafeName": "types", @@ -30420,54 +31284,74 @@ "safeName": "Types" } } - }, - "typeId": "type_types:ContainerType" - } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:AliasTypeDeclaration" }, "docs": null }, { "discriminantValue": { "name": { - "originalName": "named", + "originalName": "enum", "camelCase": { - "unsafeName": "named", - "safeName": "named" + "unsafeName": "enum", + "safeName": "enum" }, "snakeCase": { - "unsafeName": "named", - "safeName": "named" + "unsafeName": "enum", + "safeName": "enum" }, "screamingSnakeCase": { - "unsafeName": "NAMED", - "safeName": "NAMED" + "unsafeName": "ENUM", + "safeName": "ENUM" }, "pascalCase": { - "unsafeName": "Named", - "safeName": "Named" + "unsafeName": "Enum", + "safeName": "Enum" } }, - "wireValue": "named" + "wireValue": "enum" }, "shape": { "_type": "samePropertiesAsObject", "name": { - "originalName": "ResolvedNamedType", + "originalName": "EnumTypeDeclaration", "camelCase": { - "unsafeName": "resolvedNamedType", - "safeName": "resolvedNamedType" + "unsafeName": "enumTypeDeclaration", + "safeName": "enumTypeDeclaration" }, "snakeCase": { - "unsafeName": "resolved_named_type", - "safeName": "resolved_named_type" + "unsafeName": "enum_type_declaration", + "safeName": "enum_type_declaration" }, "screamingSnakeCase": { - "unsafeName": "RESOLVED_NAMED_TYPE", - "safeName": "RESOLVED_NAMED_TYPE" + "unsafeName": "ENUM_TYPE_DECLARATION", + "safeName": "ENUM_TYPE_DECLARATION" }, "pascalCase": { - "unsafeName": "ResolvedNamedType", - "safeName": "ResolvedNamedType" + "unsafeName": "EnumTypeDeclaration", + "safeName": "EnumTypeDeclaration" } }, "fernFilepath": { @@ -30513,102 +31397,57 @@ } } }, - "typeId": "type_types:ResolvedNamedType" + "typeId": "type_types:EnumTypeDeclaration" }, "docs": null }, { "discriminantValue": { "name": { - "originalName": "primitive", + "originalName": "object", "camelCase": { - "unsafeName": "primitive", - "safeName": "primitive" + "unsafeName": "object", + "safeName": "object" }, "snakeCase": { - "unsafeName": "primitive", - "safeName": "primitive" + "unsafeName": "object", + "safeName": "object" }, "screamingSnakeCase": { - "unsafeName": "PRIMITIVE", - "safeName": "PRIMITIVE" + "unsafeName": "OBJECT", + "safeName": "OBJECT" }, "pascalCase": { - "unsafeName": "Primitive", - "safeName": "Primitive" + "unsafeName": "Object", + "safeName": "Object" } }, - "wireValue": "primitive" + "wireValue": "object" }, "shape": { - "_type": "singleProperty", + "_type": "samePropertiesAsObject", "name": { - "name": { - "originalName": "primitive", - "camelCase": { - "unsafeName": "primitive", - "safeName": "primitive" - }, - "snakeCase": { - "unsafeName": "primitive", - "safeName": "primitive" - }, - "screamingSnakeCase": { - "unsafeName": "PRIMITIVE", - "safeName": "PRIMITIVE" - }, - "pascalCase": { - "unsafeName": "Primitive", - "safeName": "Primitive" - } + "originalName": "ObjectTypeDeclaration", + "camelCase": { + "unsafeName": "objectTypeDeclaration", + "safeName": "objectTypeDeclaration" }, - "wireValue": "primitive" - }, - "type": { - "_type": "named", - "name": { - "originalName": "PrimitiveType", - "camelCase": { - "unsafeName": "primitiveType", - "safeName": "primitiveType" - }, - "snakeCase": { - "unsafeName": "primitive_type", - "safeName": "primitive_type" - }, - "screamingSnakeCase": { - "unsafeName": "PRIMITIVE_TYPE", - "safeName": "PRIMITIVE_TYPE" - }, - "pascalCase": { - "unsafeName": "PrimitiveType", - "safeName": "PrimitiveType" - } + "snakeCase": { + "unsafeName": "object_type_declaration", + "safeName": "object_type_declaration" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { + "screamingSnakeCase": { + "unsafeName": "OBJECT_TYPE_DECLARATION", + "safeName": "OBJECT_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "ObjectTypeDeclaration", + "safeName": "ObjectTypeDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { "originalName": "types", "camelCase": { "unsafeName": "types", @@ -30627,79 +31466,270 @@ "safeName": "Types" } } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ObjectTypeDeclaration" + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "union", + "camelCase": { + "unsafeName": "union", + "safeName": "union" }, - "typeId": "type_types:PrimitiveType" - } + "snakeCase": { + "unsafeName": "union", + "safeName": "union" + }, + "screamingSnakeCase": { + "unsafeName": "UNION", + "safeName": "UNION" + }, + "pascalCase": { + "unsafeName": "Union", + "safeName": "Union" + } + }, + "wireValue": "union" + }, + "shape": { + "_type": "samePropertiesAsObject", + "name": { + "originalName": "UnionTypeDeclaration", + "camelCase": { + "unsafeName": "unionTypeDeclaration", + "safeName": "unionTypeDeclaration" + }, + "snakeCase": { + "unsafeName": "union_type_declaration", + "safeName": "union_type_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "UNION_TYPE_DECLARATION", + "safeName": "UNION_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "UnionTypeDeclaration", + "safeName": "UnionTypeDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:UnionTypeDeclaration" }, "docs": null }, { "discriminantValue": { "name": { - "originalName": "unknown", + "originalName": "undiscriminatedUnion", "camelCase": { - "unsafeName": "unknown", - "safeName": "unknown" + "unsafeName": "undiscriminatedUnion", + "safeName": "undiscriminatedUnion" }, "snakeCase": { - "unsafeName": "unknown", - "safeName": "unknown" + "unsafeName": "undiscriminated_union", + "safeName": "undiscriminated_union" }, "screamingSnakeCase": { - "unsafeName": "UNKNOWN", - "safeName": "UNKNOWN" + "unsafeName": "UNDISCRIMINATED_UNION", + "safeName": "UNDISCRIMINATED_UNION" }, "pascalCase": { - "unsafeName": "Unknown", - "safeName": "Unknown" + "unsafeName": "UndiscriminatedUnion", + "safeName": "UndiscriminatedUnion" } }, - "wireValue": "unknown" + "wireValue": "undiscriminatedUnion" }, "shape": { - "_type": "noProperties" + "_type": "samePropertiesAsObject", + "name": { + "originalName": "UndiscriminatedUnionTypeDeclaration", + "camelCase": { + "unsafeName": "undiscriminatedUnionTypeDeclaration", + "safeName": "undiscriminatedUnionTypeDeclaration" + }, + "snakeCase": { + "unsafeName": "undiscriminated_union_type_declaration", + "safeName": "undiscriminated_union_type_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "UNDISCRIMINATED_UNION_TYPE_DECLARATION", + "safeName": "UNDISCRIMINATED_UNION_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "UndiscriminatedUnionTypeDeclaration", + "safeName": "UndiscriminatedUnionTypeDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:UndiscriminatedUnionTypeDeclaration" }, "docs": null } ] }, "referencedTypes": [ - "type_types:ContainerType", + "type_types:AliasTypeDeclaration", "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:PrimitiveType", - "type_types:MapType", - "type_types:Literal", + "type_types:ResolvedTypeReference", "type_types:ResolvedNamedType", - "type_types:ShapeType" + "type_types:ShapeType", + "type_types:EnumTypeDeclaration", + "type_types:EnumValue", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_commons:NameAndWireValue", + "type_types:ObjectTypeDeclaration", + "type_types:ObjectProperty", + "type_types:UnionTypeDeclaration", + "type_types:SingleUnionType", + "type_types:SingleUnionTypeProperties", + "type_types:SingleUnionTypeProperty", + "type_types:UndiscriminatedUnionTypeDeclaration", + "type_types:UndiscriminatedUnionMember" ], "examples": [], "availability": null, "docs": null }, - "type_types:ResolvedNamedType": { + "type_types:AliasTypeDeclaration": { "name": { "name": { - "originalName": "ResolvedNamedType", + "originalName": "AliasTypeDeclaration", "camelCase": { - "unsafeName": "resolvedNamedType", - "safeName": "resolvedNamedType" + "unsafeName": "aliasTypeDeclaration", + "safeName": "aliasTypeDeclaration" }, "snakeCase": { - "unsafeName": "resolved_named_type", - "safeName": "resolved_named_type" + "unsafeName": "alias_type_declaration", + "safeName": "alias_type_declaration" }, "screamingSnakeCase": { - "unsafeName": "RESOLVED_NAMED_TYPE", - "safeName": "RESOLVED_NAMED_TYPE" + "unsafeName": "ALIAS_TYPE_DECLARATION", + "safeName": "ALIAS_TYPE_DECLARATION" }, "pascalCase": { - "unsafeName": "ResolvedNamedType", - "safeName": "ResolvedNamedType" + "unsafeName": "AliasTypeDeclaration", + "safeName": "AliasTypeDeclaration" } }, "fernFilepath": { @@ -30745,7 +31775,7 @@ } } }, - "typeId": "type_types:ResolvedNamedType" + "typeId": "type_types:AliasTypeDeclaration" }, "shape": { "_type": "object", @@ -30754,45 +31784,45 @@ { "name": { "name": { - "originalName": "name", + "originalName": "aliasOf", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "aliasOf", + "safeName": "aliasOf" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "alias_of", + "safeName": "alias_of" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "ALIAS_OF", + "safeName": "ALIAS_OF" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "AliasOf", + "safeName": "AliasOf" } }, - "wireValue": "name" + "wireValue": "aliasOf" }, "valueType": { "_type": "named", "name": { - "originalName": "DeclaredTypeName", + "originalName": "TypeReference", "camelCase": { - "unsafeName": "declaredTypeName", - "safeName": "declaredTypeName" + "unsafeName": "typeReference", + "safeName": "typeReference" }, "snakeCase": { - "unsafeName": "declared_type_name", - "safeName": "declared_type_name" + "unsafeName": "type_reference", + "safeName": "type_reference" }, "screamingSnakeCase": { - "unsafeName": "DECLARED_TYPE_NAME", - "safeName": "DECLARED_TYPE_NAME" + "unsafeName": "TYPE_REFERENCE", + "safeName": "TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "DeclaredTypeName", - "safeName": "DeclaredTypeName" + "unsafeName": "TypeReference", + "safeName": "TypeReference" } }, "fernFilepath": { @@ -30838,7 +31868,7 @@ } } }, - "typeId": "type_types:DeclaredTypeName" + "typeId": "type_types:TypeReference" }, "availability": null, "docs": null @@ -30846,45 +31876,45 @@ { "name": { "name": { - "originalName": "shape", + "originalName": "resolvedType", "camelCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "resolvedType", + "safeName": "resolvedType" }, "snakeCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "resolved_type", + "safeName": "resolved_type" }, "screamingSnakeCase": { - "unsafeName": "SHAPE", - "safeName": "SHAPE" + "unsafeName": "RESOLVED_TYPE", + "safeName": "RESOLVED_TYPE" }, "pascalCase": { - "unsafeName": "Shape", - "safeName": "Shape" + "unsafeName": "ResolvedType", + "safeName": "ResolvedType" } }, - "wireValue": "shape" + "wireValue": "resolvedType" }, "valueType": { "_type": "named", "name": { - "originalName": "ShapeType", + "originalName": "ResolvedTypeReference", "camelCase": { - "unsafeName": "shapeType", - "safeName": "shapeType" + "unsafeName": "resolvedTypeReference", + "safeName": "resolvedTypeReference" }, "snakeCase": { - "unsafeName": "shape_type", - "safeName": "shape_type" + "unsafeName": "resolved_type_reference", + "safeName": "resolved_type_reference" }, "screamingSnakeCase": { - "unsafeName": "SHAPE_TYPE", - "safeName": "SHAPE_TYPE" + "unsafeName": "RESOLVED_TYPE_REFERENCE", + "safeName": "RESOLVED_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "ShapeType", - "safeName": "ShapeType" + "unsafeName": "ResolvedTypeReference", + "safeName": "ResolvedTypeReference" } }, "fernFilepath": { @@ -30930,7 +31960,7 @@ } } }, - "typeId": "type_types:ShapeType" + "typeId": "type_types:ResolvedTypeReference" }, "availability": null, "docs": null @@ -30938,36 +31968,43 @@ ] }, "referencedTypes": [ + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", "type_commons:Name", "type_commons:SafeAndUnsafeString", + "type_types:PrimitiveType", + "type_types:ResolvedTypeReference", + "type_types:ResolvedNamedType", "type_types:ShapeType" ], "examples": [], "availability": null, "docs": null }, - "type_types:ShapeType": { + "type_types:ResolvedTypeReference": { "name": { "name": { - "originalName": "ShapeType", + "originalName": "ResolvedTypeReference", "camelCase": { - "unsafeName": "shapeType", - "safeName": "shapeType" + "unsafeName": "resolvedTypeReference", + "safeName": "resolvedTypeReference" }, "snakeCase": { - "unsafeName": "shape_type", - "safeName": "shape_type" + "unsafeName": "resolved_type_reference", + "safeName": "resolved_type_reference" }, "screamingSnakeCase": { - "unsafeName": "SHAPE_TYPE", - "safeName": "SHAPE_TYPE" + "unsafeName": "RESOLVED_TYPE_REFERENCE", + "safeName": "RESOLVED_TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "ShapeType", - "safeName": "ShapeType" + "unsafeName": "ResolvedTypeReference", + "safeName": "ResolvedTypeReference" } }, "fernFilepath": { @@ -31013,141 +32050,425 @@ } } }, - "typeId": "type_types:ShapeType" + "typeId": "type_types:ResolvedTypeReference" }, "shape": { - "_type": "enum", - "values": [ + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "_type" + }, + "extends": [], + "baseProperties": [], + "types": [ { - "name": { + "discriminantValue": { "name": { - "originalName": "ENUM", + "originalName": "container", "camelCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "container", + "safeName": "container" }, "snakeCase": { - "unsafeName": "enum", - "safeName": "enum" + "unsafeName": "container", + "safeName": "container" }, "screamingSnakeCase": { - "unsafeName": "ENUM", - "safeName": "ENUM" + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" }, "pascalCase": { - "unsafeName": "Enum", - "safeName": "Enum" + "unsafeName": "Container", + "safeName": "Container" } }, - "wireValue": "ENUM" + "wireValue": "container" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "container", + "camelCase": { + "unsafeName": "container", + "safeName": "container" + }, + "snakeCase": { + "unsafeName": "container", + "safeName": "container" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER", + "safeName": "CONTAINER" + }, + "pascalCase": { + "unsafeName": "Container", + "safeName": "Container" + } + }, + "wireValue": "container" + }, + "type": { + "_type": "named", + "name": { + "originalName": "ContainerType", + "camelCase": { + "unsafeName": "containerType", + "safeName": "containerType" + }, + "snakeCase": { + "unsafeName": "container_type", + "safeName": "container_type" + }, + "screamingSnakeCase": { + "unsafeName": "CONTAINER_TYPE", + "safeName": "CONTAINER_TYPE" + }, + "pascalCase": { + "unsafeName": "ContainerType", + "safeName": "ContainerType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ContainerType" + } }, - "availability": null, "docs": null }, { - "name": { + "discriminantValue": { "name": { - "originalName": "OBJECT", + "originalName": "named", "camelCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "named", + "safeName": "named" }, "snakeCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "named", + "safeName": "named" }, "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" + "unsafeName": "NAMED", + "safeName": "NAMED" }, "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" + "unsafeName": "Named", + "safeName": "Named" } }, - "wireValue": "OBJECT" + "wireValue": "named" + }, + "shape": { + "_type": "samePropertiesAsObject", + "name": { + "originalName": "ResolvedNamedType", + "camelCase": { + "unsafeName": "resolvedNamedType", + "safeName": "resolvedNamedType" + }, + "snakeCase": { + "unsafeName": "resolved_named_type", + "safeName": "resolved_named_type" + }, + "screamingSnakeCase": { + "unsafeName": "RESOLVED_NAMED_TYPE", + "safeName": "RESOLVED_NAMED_TYPE" + }, + "pascalCase": { + "unsafeName": "ResolvedNamedType", + "safeName": "ResolvedNamedType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ResolvedNamedType" }, - "availability": null, "docs": null }, { - "name": { + "discriminantValue": { "name": { - "originalName": "UNION", + "originalName": "primitive", "camelCase": { - "unsafeName": "union", - "safeName": "union" + "unsafeName": "primitive", + "safeName": "primitive" }, "snakeCase": { - "unsafeName": "union", - "safeName": "union" + "unsafeName": "primitive", + "safeName": "primitive" }, "screamingSnakeCase": { - "unsafeName": "UNION", - "safeName": "UNION" + "unsafeName": "PRIMITIVE", + "safeName": "PRIMITIVE" }, "pascalCase": { - "unsafeName": "Union", - "safeName": "Union" + "unsafeName": "Primitive", + "safeName": "Primitive" } }, - "wireValue": "UNION" + "wireValue": "primitive" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "primitive", + "camelCase": { + "unsafeName": "primitive", + "safeName": "primitive" + }, + "snakeCase": { + "unsafeName": "primitive", + "safeName": "primitive" + }, + "screamingSnakeCase": { + "unsafeName": "PRIMITIVE", + "safeName": "PRIMITIVE" + }, + "pascalCase": { + "unsafeName": "Primitive", + "safeName": "Primitive" + } + }, + "wireValue": "primitive" + }, + "type": { + "_type": "named", + "name": { + "originalName": "PrimitiveType", + "camelCase": { + "unsafeName": "primitiveType", + "safeName": "primitiveType" + }, + "snakeCase": { + "unsafeName": "primitive_type", + "safeName": "primitive_type" + }, + "screamingSnakeCase": { + "unsafeName": "PRIMITIVE_TYPE", + "safeName": "PRIMITIVE_TYPE" + }, + "pascalCase": { + "unsafeName": "PrimitiveType", + "safeName": "PrimitiveType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:PrimitiveType" + } }, - "availability": null, "docs": null }, { - "name": { + "discriminantValue": { "name": { - "originalName": "UNDISCRIMINATED_UNION", + "originalName": "unknown", "camelCase": { - "unsafeName": "undiscriminatedUnion", - "safeName": "undiscriminatedUnion" + "unsafeName": "unknown", + "safeName": "unknown" }, "snakeCase": { - "unsafeName": "undiscriminated_union", - "safeName": "undiscriminated_union" + "unsafeName": "unknown", + "safeName": "unknown" }, "screamingSnakeCase": { - "unsafeName": "UNDISCRIMINATED_UNION", - "safeName": "UNDISCRIMINATED_UNION" + "unsafeName": "UNKNOWN", + "safeName": "UNKNOWN" }, "pascalCase": { - "unsafeName": "UndiscriminatedUnion", - "safeName": "UndiscriminatedUnion" + "unsafeName": "Unknown", + "safeName": "Unknown" } }, - "wireValue": "UNDISCRIMINATED_UNION" + "wireValue": "unknown" + }, + "shape": { + "_type": "noProperties" }, - "availability": null, "docs": null } ] }, - "referencedTypes": [], + "referencedTypes": [ + "type_types:ContainerType", + "type_types:TypeReference", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:PrimitiveType", + "type_types:MapType", + "type_types:Literal", + "type_types:ResolvedNamedType", + "type_types:ShapeType" + ], "examples": [], "availability": null, "docs": null }, - "type_types:EnumTypeDeclaration": { + "type_types:ResolvedNamedType": { "name": { "name": { - "originalName": "EnumTypeDeclaration", + "originalName": "ResolvedNamedType", "camelCase": { - "unsafeName": "enumTypeDeclaration", - "safeName": "enumTypeDeclaration" + "unsafeName": "resolvedNamedType", + "safeName": "resolvedNamedType" }, "snakeCase": { - "unsafeName": "enum_type_declaration", - "safeName": "enum_type_declaration" + "unsafeName": "resolved_named_type", + "safeName": "resolved_named_type" }, "screamingSnakeCase": { - "unsafeName": "ENUM_TYPE_DECLARATION", - "safeName": "ENUM_TYPE_DECLARATION" + "unsafeName": "RESOLVED_NAMED_TYPE", + "safeName": "RESOLVED_NAMED_TYPE" }, "pascalCase": { - "unsafeName": "EnumTypeDeclaration", - "safeName": "EnumTypeDeclaration" + "unsafeName": "ResolvedNamedType", + "safeName": "ResolvedNamedType" } }, "fernFilepath": { @@ -31193,7 +32514,7 @@ } } }, - "typeId": "type_types:EnumTypeDeclaration" + "typeId": "type_types:ResolvedNamedType" }, "shape": { "_type": "object", @@ -31202,97 +32523,183 @@ { "name": { "name": { - "originalName": "values", + "originalName": "name", "camelCase": { - "unsafeName": "values", - "safeName": "values" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "values", - "safeName": "values" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "VALUES", - "safeName": "VALUES" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "Values", - "safeName": "Values" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "values" + "wireValue": "name" }, "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "EnumValue", + "_type": "named", + "name": { + "originalName": "DeclaredTypeName", + "camelCase": { + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" + }, + "snakeCase": { + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" + }, + "pascalCase": { + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", "camelCase": { - "unsafeName": "enumValue", - "safeName": "enumValue" + "unsafeName": "types", + "safeName": "types" }, "snakeCase": { - "unsafeName": "enum_value", - "safeName": "enum_value" + "unsafeName": "types", + "safeName": "types" }, "screamingSnakeCase": { - "unsafeName": "ENUM_VALUE", - "safeName": "ENUM_VALUE" + "unsafeName": "TYPES", + "safeName": "TYPES" }, "pascalCase": { - "unsafeName": "EnumValue", - "safeName": "EnumValue" + "unsafeName": "Types", + "safeName": "Types" } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:DeclaredTypeName" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "shape", + "camelCase": { + "unsafeName": "shape", + "safeName": "shape" + }, + "snakeCase": { + "unsafeName": "shape", + "safeName": "shape" + }, + "screamingSnakeCase": { + "unsafeName": "SHAPE", + "safeName": "SHAPE" + }, + "pascalCase": { + "unsafeName": "Shape", + "safeName": "Shape" + } + }, + "wireValue": "shape" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ShapeType", + "camelCase": { + "unsafeName": "shapeType", + "safeName": "shapeType" + }, + "snakeCase": { + "unsafeName": "shape_type", + "safeName": "shape_type" + }, + "screamingSnakeCase": { + "unsafeName": "SHAPE_TYPE", + "safeName": "SHAPE_TYPE" + }, + "pascalCase": { + "unsafeName": "ShapeType", + "safeName": "ShapeType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" }, - "typeId": "type_types:EnumValue" + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } } - } + }, + "typeId": "type_types:ShapeType" }, "availability": null, "docs": null @@ -31300,38 +32707,36 @@ ] }, "referencedTypes": [ - "type_types:EnumValue", - "type_commons:Declaration", - "type_commons:WithDocs", - "type_commons:Availability", - "type_commons:AvailabilityStatus", - "type_commons:NameAndWireValue", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", "type_commons:Name", - "type_commons:SafeAndUnsafeString" + "type_commons:SafeAndUnsafeString", + "type_types:ShapeType" ], "examples": [], "availability": null, "docs": null }, - "type_types:EnumValue": { + "type_types:ShapeType": { "name": { "name": { - "originalName": "EnumValue", + "originalName": "ShapeType", "camelCase": { - "unsafeName": "enumValue", - "safeName": "enumValue" + "unsafeName": "shapeType", + "safeName": "shapeType" }, "snakeCase": { - "unsafeName": "enum_value", - "safeName": "enum_value" + "unsafeName": "shape_type", + "safeName": "shape_type" }, "screamingSnakeCase": { - "unsafeName": "ENUM_VALUE", - "safeName": "ENUM_VALUE" + "unsafeName": "SHAPE_TYPE", + "safeName": "SHAPE_TYPE" }, "pascalCase": { - "unsafeName": "EnumValue", - "safeName": "EnumValue" + "unsafeName": "ShapeType", + "safeName": "ShapeType" } }, "fernFilepath": { @@ -31377,11 +32782,375 @@ } } }, - "typeId": "type_types:EnumValue" + "typeId": "type_types:ShapeType" }, "shape": { - "_type": "object", - "extends": [ + "_type": "enum", + "values": [ + { + "name": { + "name": { + "originalName": "ENUM", + "camelCase": { + "unsafeName": "enum", + "safeName": "enum" + }, + "snakeCase": { + "unsafeName": "enum", + "safeName": "enum" + }, + "screamingSnakeCase": { + "unsafeName": "ENUM", + "safeName": "ENUM" + }, + "pascalCase": { + "unsafeName": "Enum", + "safeName": "Enum" + } + }, + "wireValue": "ENUM" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "OBJECT", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + }, + "wireValue": "OBJECT" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "UNION", + "camelCase": { + "unsafeName": "union", + "safeName": "union" + }, + "snakeCase": { + "unsafeName": "union", + "safeName": "union" + }, + "screamingSnakeCase": { + "unsafeName": "UNION", + "safeName": "UNION" + }, + "pascalCase": { + "unsafeName": "Union", + "safeName": "Union" + } + }, + "wireValue": "UNION" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "UNDISCRIMINATED_UNION", + "camelCase": { + "unsafeName": "undiscriminatedUnion", + "safeName": "undiscriminatedUnion" + }, + "snakeCase": { + "unsafeName": "undiscriminated_union", + "safeName": "undiscriminated_union" + }, + "screamingSnakeCase": { + "unsafeName": "UNDISCRIMINATED_UNION", + "safeName": "UNDISCRIMINATED_UNION" + }, + "pascalCase": { + "unsafeName": "UndiscriminatedUnion", + "safeName": "UndiscriminatedUnion" + } + }, + "wireValue": "UNDISCRIMINATED_UNION" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:EnumTypeDeclaration": { + "name": { + "name": { + "originalName": "EnumTypeDeclaration", + "camelCase": { + "unsafeName": "enumTypeDeclaration", + "safeName": "enumTypeDeclaration" + }, + "snakeCase": { + "unsafeName": "enum_type_declaration", + "safeName": "enum_type_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "ENUM_TYPE_DECLARATION", + "safeName": "ENUM_TYPE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "EnumTypeDeclaration", + "safeName": "EnumTypeDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:EnumTypeDeclaration" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "values", + "camelCase": { + "unsafeName": "values", + "safeName": "values" + }, + "snakeCase": { + "unsafeName": "values", + "safeName": "values" + }, + "screamingSnakeCase": { + "unsafeName": "VALUES", + "safeName": "VALUES" + }, + "pascalCase": { + "unsafeName": "Values", + "safeName": "Values" + } + }, + "wireValue": "values" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "EnumValue", + "camelCase": { + "unsafeName": "enumValue", + "safeName": "enumValue" + }, + "snakeCase": { + "unsafeName": "enum_value", + "safeName": "enum_value" + }, + "screamingSnakeCase": { + "unsafeName": "ENUM_VALUE", + "safeName": "ENUM_VALUE" + }, + "pascalCase": { + "unsafeName": "EnumValue", + "safeName": "EnumValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:EnumValue" + } + } + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_types:EnumValue", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:EnumValue": { + "name": { + "name": { + "originalName": "EnumValue", + "camelCase": { + "unsafeName": "enumValue", + "safeName": "enumValue" + }, + "snakeCase": { + "unsafeName": "enum_value", + "safeName": "enum_value" + }, + "screamingSnakeCase": { + "unsafeName": "ENUM_VALUE", + "safeName": "ENUM_VALUE" + }, + "pascalCase": { + "unsafeName": "EnumValue", + "safeName": "EnumValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:EnumValue" + }, + "shape": { + "_type": "object", + "extends": [ { "name": { "originalName": "Declaration", @@ -40239,535 +42008,3247 @@ "file": { "originalName": "types", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleTypeReference" + } + } + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "map", + "camelCase": { + "unsafeName": "map", + "safeName": "map" + }, + "snakeCase": { + "unsafeName": "map", + "safeName": "map" + }, + "screamingSnakeCase": { + "unsafeName": "MAP", + "safeName": "MAP" + }, + "pascalCase": { + "unsafeName": "Map", + "safeName": "Map" + } + }, + "wireValue": "map" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "map", + "camelCase": { + "unsafeName": "map", + "safeName": "map" + }, + "snakeCase": { + "unsafeName": "map", + "safeName": "map" + }, + "screamingSnakeCase": { + "unsafeName": "MAP", + "safeName": "MAP" + }, + "pascalCase": { + "unsafeName": "Map", + "safeName": "Map" + } + }, + "wireValue": "map" + }, + "type": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "ExampleKeyValuePair", + "camelCase": { + "unsafeName": "exampleKeyValuePair", + "safeName": "exampleKeyValuePair" + }, + "snakeCase": { + "unsafeName": "example_key_value_pair", + "safeName": "example_key_value_pair" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_KEY_VALUE_PAIR", + "safeName": "EXAMPLE_KEY_VALUE_PAIR" + }, + "pascalCase": { + "unsafeName": "ExampleKeyValuePair", + "safeName": "ExampleKeyValuePair" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleKeyValuePair" + } + } + } + }, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:ExampleKeyValuePair": { + "name": { + "name": { + "originalName": "ExampleKeyValuePair", + "camelCase": { + "unsafeName": "exampleKeyValuePair", + "safeName": "exampleKeyValuePair" + }, + "snakeCase": { + "unsafeName": "example_key_value_pair", + "safeName": "example_key_value_pair" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_KEY_VALUE_PAIR", + "safeName": "EXAMPLE_KEY_VALUE_PAIR" + }, + "pascalCase": { + "unsafeName": "ExampleKeyValuePair", + "safeName": "ExampleKeyValuePair" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleKeyValuePair" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "key", + "camelCase": { + "unsafeName": "key", + "safeName": "key" + }, + "snakeCase": { + "unsafeName": "key", + "safeName": "key" + }, + "screamingSnakeCase": { + "unsafeName": "KEY", + "safeName": "KEY" + }, + "pascalCase": { + "unsafeName": "Key", + "safeName": "Key" + } + }, + "wireValue": "key" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ExampleTypeReference", + "camelCase": { + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" + }, + "snakeCase": { + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" + }, + "pascalCase": { + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleTypeReference" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "value", + "camelCase": { + "unsafeName": "value", + "safeName": "value" + }, + "snakeCase": { + "unsafeName": "value", + "safeName": "value" + }, + "screamingSnakeCase": { + "unsafeName": "VALUE", + "safeName": "VALUE" + }, + "pascalCase": { + "unsafeName": "Value", + "safeName": "Value" + } + }, + "wireValue": "value" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ExampleTypeReference", + "camelCase": { + "unsafeName": "exampleTypeReference", + "safeName": "exampleTypeReference" + }, + "snakeCase": { + "unsafeName": "example_type_reference", + "safeName": "example_type_reference" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_TYPE_REFERENCE", + "safeName": "EXAMPLE_TYPE_REFERENCE" + }, + "pascalCase": { + "unsafeName": "ExampleTypeReference", + "safeName": "ExampleTypeReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleTypeReference" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:ExamplePrimitive": { + "name": { + "name": { + "originalName": "ExamplePrimitive", + "camelCase": { + "unsafeName": "examplePrimitive", + "safeName": "examplePrimitive" + }, + "snakeCase": { + "unsafeName": "example_primitive", + "safeName": "example_primitive" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_PRIMITIVE", + "safeName": "EXAMPLE_PRIMITIVE" + }, + "pascalCase": { + "unsafeName": "ExamplePrimitive", + "safeName": "ExamplePrimitive" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExamplePrimitive" + }, + "shape": { + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "extends": [], + "baseProperties": [], + "types": [ + { + "discriminantValue": { + "name": { + "originalName": "integer", + "camelCase": { + "unsafeName": "integer", + "safeName": "integer" + }, + "snakeCase": { + "unsafeName": "integer", + "safeName": "integer" + }, + "screamingSnakeCase": { + "unsafeName": "INTEGER", + "safeName": "INTEGER" + }, + "pascalCase": { + "unsafeName": "Integer", + "safeName": "Integer" + } + }, + "wireValue": "integer" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "integer", + "camelCase": { + "unsafeName": "integer", + "safeName": "integer" + }, + "snakeCase": { + "unsafeName": "integer", + "safeName": "integer" + }, + "screamingSnakeCase": { + "unsafeName": "INTEGER", + "safeName": "INTEGER" + }, + "pascalCase": { + "unsafeName": "Integer", + "safeName": "Integer" + } + }, + "wireValue": "integer" + }, + "type": { + "_type": "primitive", + "primitive": "INTEGER" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "double", + "camelCase": { + "unsafeName": "double", + "safeName": "double" + }, + "snakeCase": { + "unsafeName": "double", + "safeName": "double" + }, + "screamingSnakeCase": { + "unsafeName": "DOUBLE", + "safeName": "DOUBLE" + }, + "pascalCase": { + "unsafeName": "Double", + "safeName": "Double" + } + }, + "wireValue": "double" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "double", + "camelCase": { + "unsafeName": "double", + "safeName": "double" + }, + "snakeCase": { + "unsafeName": "double", + "safeName": "double" + }, + "screamingSnakeCase": { + "unsafeName": "DOUBLE", + "safeName": "DOUBLE" + }, + "pascalCase": { + "unsafeName": "Double", + "safeName": "Double" + } + }, + "wireValue": "double" + }, + "type": { + "_type": "primitive", + "primitive": "DOUBLE" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "string", + "camelCase": { + "unsafeName": "string", + "safeName": "string" + }, + "snakeCase": { + "unsafeName": "string", + "safeName": "string" + }, + "screamingSnakeCase": { + "unsafeName": "STRING", + "safeName": "STRING" + }, + "pascalCase": { + "unsafeName": "String", + "safeName": "String" + } + }, + "wireValue": "string" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "string", + "camelCase": { + "unsafeName": "string", + "safeName": "string" + }, + "snakeCase": { + "unsafeName": "string", + "safeName": "string" + }, + "screamingSnakeCase": { + "unsafeName": "STRING", + "safeName": "STRING" + }, + "pascalCase": { + "unsafeName": "String", + "safeName": "String" + } + }, + "wireValue": "string" + }, + "type": { + "_type": "named", + "name": { + "originalName": "EscapedString", + "camelCase": { + "unsafeName": "escapedString", + "safeName": "escapedString" + }, + "snakeCase": { + "unsafeName": "escaped_string", + "safeName": "escaped_string" + }, + "screamingSnakeCase": { + "unsafeName": "ESCAPED_STRING", + "safeName": "ESCAPED_STRING" + }, + "pascalCase": { + "unsafeName": "EscapedString", + "safeName": "EscapedString" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:EscapedString" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "boolean", + "camelCase": { + "unsafeName": "boolean", + "safeName": "boolean" + }, + "snakeCase": { + "unsafeName": "boolean", + "safeName": "boolean" + }, + "screamingSnakeCase": { + "unsafeName": "BOOLEAN", + "safeName": "BOOLEAN" + }, + "pascalCase": { + "unsafeName": "Boolean", + "safeName": "Boolean" + } + }, + "wireValue": "boolean" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "boolean", + "camelCase": { + "unsafeName": "boolean", + "safeName": "boolean" + }, + "snakeCase": { + "unsafeName": "boolean", + "safeName": "boolean" + }, + "screamingSnakeCase": { + "unsafeName": "BOOLEAN", + "safeName": "BOOLEAN" + }, + "pascalCase": { + "unsafeName": "Boolean", + "safeName": "Boolean" + } + }, + "wireValue": "boolean" + }, + "type": { + "_type": "primitive", + "primitive": "BOOLEAN" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "long", + "camelCase": { + "unsafeName": "long", + "safeName": "long" + }, + "snakeCase": { + "unsafeName": "long", + "safeName": "long" + }, + "screamingSnakeCase": { + "unsafeName": "LONG", + "safeName": "LONG" + }, + "pascalCase": { + "unsafeName": "Long", + "safeName": "Long" + } + }, + "wireValue": "long" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "long", + "camelCase": { + "unsafeName": "long", + "safeName": "long" + }, + "snakeCase": { + "unsafeName": "long", + "safeName": "long" + }, + "screamingSnakeCase": { + "unsafeName": "LONG", + "safeName": "LONG" + }, + "pascalCase": { + "unsafeName": "Long", + "safeName": "Long" + } + }, + "wireValue": "long" + }, + "type": { + "_type": "primitive", + "primitive": "LONG" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "datetime", + "camelCase": { + "unsafeName": "datetime", + "safeName": "datetime" + }, + "snakeCase": { + "unsafeName": "datetime", + "safeName": "datetime" + }, + "screamingSnakeCase": { + "unsafeName": "DATETIME", + "safeName": "DATETIME" + }, + "pascalCase": { + "unsafeName": "Datetime", + "safeName": "Datetime" + } + }, + "wireValue": "datetime" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "datetime", + "camelCase": { + "unsafeName": "datetime", + "safeName": "datetime" + }, + "snakeCase": { + "unsafeName": "datetime", + "safeName": "datetime" + }, + "screamingSnakeCase": { + "unsafeName": "DATETIME", + "safeName": "DATETIME" + }, + "pascalCase": { + "unsafeName": "Datetime", + "safeName": "Datetime" + } + }, + "wireValue": "datetime" + }, + "type": { + "_type": "primitive", + "primitive": "DATE_TIME" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "date", + "camelCase": { + "unsafeName": "date", + "safeName": "date" + }, + "snakeCase": { + "unsafeName": "date", + "safeName": "date" + }, + "screamingSnakeCase": { + "unsafeName": "DATE", + "safeName": "DATE" + }, + "pascalCase": { + "unsafeName": "Date", + "safeName": "Date" + } + }, + "wireValue": "date" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "date", + "camelCase": { + "unsafeName": "date", + "safeName": "date" + }, + "snakeCase": { + "unsafeName": "date", + "safeName": "date" + }, + "screamingSnakeCase": { + "unsafeName": "DATE", + "safeName": "DATE" + }, + "pascalCase": { + "unsafeName": "Date", + "safeName": "Date" + } + }, + "wireValue": "date" + }, + "type": { + "_type": "primitive", + "primitive": "DATE" + } + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "uuid", + "camelCase": { + "unsafeName": "uuid", + "safeName": "uuid" + }, + "snakeCase": { + "unsafeName": "uuid", + "safeName": "uuid" + }, + "screamingSnakeCase": { + "unsafeName": "UUID", + "safeName": "UUID" + }, + "pascalCase": { + "unsafeName": "Uuid", + "safeName": "Uuid" + } + }, + "wireValue": "uuid" + }, + "shape": { + "_type": "singleProperty", + "name": { + "name": { + "originalName": "uuid", + "camelCase": { + "unsafeName": "uuid", + "safeName": "uuid" + }, + "snakeCase": { + "unsafeName": "uuid", + "safeName": "uuid" + }, + "screamingSnakeCase": { + "unsafeName": "UUID", + "safeName": "UUID" + }, + "pascalCase": { + "unsafeName": "Uuid", + "safeName": "Uuid" + } + }, + "wireValue": "uuid" + }, + "type": { + "_type": "primitive", + "primitive": "UUID" + } + }, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_commons:EscapedString" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:ExampleNamedType": { + "name": { + "name": { + "originalName": "ExampleNamedType", + "camelCase": { + "unsafeName": "exampleNamedType", + "safeName": "exampleNamedType" + }, + "snakeCase": { + "unsafeName": "example_named_type", + "safeName": "example_named_type" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_NAMED_TYPE", + "safeName": "EXAMPLE_NAMED_TYPE" + }, + "pascalCase": { + "unsafeName": "ExampleNamedType", + "safeName": "ExampleNamedType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleNamedType" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "typeName", + "camelCase": { + "unsafeName": "typeName", + "safeName": "typeName" + }, + "snakeCase": { + "unsafeName": "type_name", + "safeName": "type_name" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_NAME", + "safeName": "TYPE_NAME" + }, + "pascalCase": { + "unsafeName": "TypeName", + "safeName": "TypeName" + } + }, + "wireValue": "typeName" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "DeclaredTypeName", + "camelCase": { + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" + }, + "snakeCase": { + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" + }, + "pascalCase": { + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:DeclaredTypeName" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "shape", + "camelCase": { + "unsafeName": "shape", + "safeName": "shape" + }, + "snakeCase": { + "unsafeName": "shape", + "safeName": "shape" + }, + "screamingSnakeCase": { + "unsafeName": "SHAPE", + "safeName": "SHAPE" + }, + "pascalCase": { + "unsafeName": "Shape", + "safeName": "Shape" + } + }, + "wireValue": "shape" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ExampleTypeShape", + "camelCase": { + "unsafeName": "exampleTypeShape", + "safeName": "exampleTypeShape" + }, + "snakeCase": { + "unsafeName": "example_type_shape", + "safeName": "example_type_shape" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_TYPE_SHAPE", + "safeName": "EXAMPLE_TYPE_SHAPE" + }, + "pascalCase": { + "unsafeName": "ExampleTypeShape", + "safeName": "ExampleTypeShape" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleTypeShape" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:ExampleEnumType", + "type_commons:NameAndWireValue", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_types:ExampleObjectTypeWithTypeId": { + "name": { + "name": { + "originalName": "ExampleObjectTypeWithTypeId", + "camelCase": { + "unsafeName": "exampleObjectTypeWithTypeId", + "safeName": "exampleObjectTypeWithTypeId" + }, + "snakeCase": { + "unsafeName": "example_object_type_with_type_id", + "safeName": "example_object_type_with_type_id" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_OBJECT_TYPE_WITH_TYPE_ID", + "safeName": "EXAMPLE_OBJECT_TYPE_WITH_TYPE_ID" + }, + "pascalCase": { + "unsafeName": "ExampleObjectTypeWithTypeId", + "safeName": "ExampleObjectTypeWithTypeId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleObjectTypeWithTypeId" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "typeId", + "camelCase": { + "unsafeName": "typeId", + "safeName": "typeId" + }, + "snakeCase": { + "unsafeName": "type_id", + "safeName": "type_id" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" + }, + "pascalCase": { + "unsafeName": "TypeId", + "safeName": "TypeId" + } + }, + "wireValue": "typeId" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "TypeId", + "camelCase": { + "unsafeName": "typeId", + "safeName": "typeId" + }, + "snakeCase": { + "unsafeName": "type_id", + "safeName": "type_id" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_ID", + "safeName": "TYPE_ID" + }, + "pascalCase": { + "unsafeName": "TypeId", + "safeName": "TypeId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:TypeId" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "object", + "camelCase": { + "unsafeName": "object", + "safeName": "object" + }, + "snakeCase": { + "unsafeName": "object", + "safeName": "object" + }, + "screamingSnakeCase": { + "unsafeName": "OBJECT", + "safeName": "OBJECT" + }, + "pascalCase": { + "unsafeName": "Object", + "safeName": "Object" + } + }, + "wireValue": "object" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "ExampleObjectType", + "camelCase": { + "unsafeName": "exampleObjectType", + "safeName": "exampleObjectType" + }, + "snakeCase": { + "unsafeName": "example_object_type", + "safeName": "example_object_type" + }, + "screamingSnakeCase": { + "unsafeName": "EXAMPLE_OBJECT_TYPE", + "safeName": "EXAMPLE_OBJECT_TYPE" + }, + "pascalCase": { + "unsafeName": "ExampleObjectType", + "safeName": "ExampleObjectType" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:ExampleObjectType" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_commons:TypeId", + "type_types:ExampleObjectType", + "type_types:ExampleObjectProperty", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:ExampleTypeReference", + "type_commons:WithJsonExample", + "type_types:ExampleTypeReferenceShape", + "type_types:ExamplePrimitive", + "type_commons:EscapedString", + "type_types:ExampleContainer", + "type_types:ExampleKeyValuePair", + "type_types:ExampleNamedType", + "type_types:DeclaredTypeName", + "type_commons:FernFilepath", + "type_types:ExampleTypeShape", + "type_types:ExampleAliasType", + "type_types:ExampleEnumType", + "type_types:ExampleUnionType", + "type_types:ExampleSingleUnionType", + "type_types:ExampleSingleUnionTypeProperties", + "type_types:ExampleObjectTypeWithTypeId", + "type_types:ExampleUndiscriminatedUnionType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_variables:VariableId": { + "name": { + "name": { + "originalName": "VariableId", + "camelCase": { + "unsafeName": "variableId", + "safeName": "variableId" + }, + "snakeCase": { + "unsafeName": "variable_id", + "safeName": "variable_id" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLE_ID", + "safeName": "VARIABLE_ID" + }, + "pascalCase": { + "unsafeName": "VariableId", + "safeName": "VariableId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + ], + "packagePath": [], + "file": { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + }, + "typeId": "type_variables:VariableId" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": "STRING" + }, + "resolvedType": { + "_type": "primitive", + "primitive": "STRING" + } + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, + "type_variables:VariableDeclaration": { + "name": { + "name": { + "originalName": "VariableDeclaration", + "camelCase": { + "unsafeName": "variableDeclaration", + "safeName": "variableDeclaration" + }, + "snakeCase": { + "unsafeName": "variable_declaration", + "safeName": "variable_declaration" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLE_DECLARATION", + "safeName": "VARIABLE_DECLARATION" + }, + "pascalCase": { + "unsafeName": "VariableDeclaration", + "safeName": "VariableDeclaration" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + ], + "packagePath": [], + "file": { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + }, + "typeId": "type_variables:VariableDeclaration" + }, + "shape": { + "_type": "object", + "extends": [ + { + "name": { + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" + }, + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithDocs" + } + ], + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "Id", + "safeName": "Id" + } + }, + "wireValue": "id" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "VariableId", + "camelCase": { + "unsafeName": "variableId", + "safeName": "variableId" + }, + "snakeCase": { + "unsafeName": "variable_id", + "safeName": "variable_id" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLE_ID", + "safeName": "VARIABLE_ID" + }, + "pascalCase": { + "unsafeName": "VariableId", + "safeName": "VariableId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + ], + "packagePath": [], + "file": { + "originalName": "variables", + "camelCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "snakeCase": { + "unsafeName": "variables", + "safeName": "variables" + }, + "screamingSnakeCase": { + "unsafeName": "VARIABLES", + "safeName": "VARIABLES" + }, + "pascalCase": { + "unsafeName": "Variables", + "safeName": "Variables" + } + } + }, + "typeId": "type_variables:VariableId" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Name" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "TypeReference", + "camelCase": { + "unsafeName": "typeReference", + "safeName": "typeReference" + }, + "snakeCase": { + "unsafeName": "type_reference", + "safeName": "type_reference" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_REFERENCE", + "safeName": "TYPE_REFERENCE" + }, + "pascalCase": { + "unsafeName": "TypeReference", + "safeName": "TypeReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:TypeReference" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_commons:WithDocs", + "type_variables:VariableId", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_webhooks:WebhookGroup": { + "name": { + "name": { + "originalName": "WebhookGroup", + "camelCase": { + "unsafeName": "webhookGroup", + "safeName": "webhookGroup" + }, + "snakeCase": { + "unsafeName": "webhook_group", + "safeName": "webhook_group" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK_GROUP", + "safeName": "WEBHOOK_GROUP" + }, + "pascalCase": { + "unsafeName": "WebhookGroup", + "safeName": "WebhookGroup" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:WebhookGroup" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "Webhook", + "camelCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "snakeCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK", + "safeName": "WEBHOOK" + }, + "pascalCase": { + "unsafeName": "Webhook", + "safeName": "Webhook" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:Webhook" + } + } + }, + "resolvedType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "Webhook", + "camelCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "snakeCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK", + "safeName": "WEBHOOK" + }, + "pascalCase": { + "unsafeName": "Webhook", + "safeName": "Webhook" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:Webhook" + } + } + } + }, + "referencedTypes": [ + "type_webhooks:Webhook", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_webhooks:WebhookName", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_webhooks:WebhookHttpMethod", + "type_http:HttpHeader", + "type_commons:NameAndWireValue", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType", + "type_webhooks:WebhookPayload", + "type_webhooks:InlinedWebhookPayload", + "type_webhooks:InlinedWebhookPayloadProperty", + "type_webhooks:WebhookPayloadReference" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_webhooks:Webhook": { + "name": { + "name": { + "originalName": "Webhook", + "camelCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "snakeCase": { + "unsafeName": "webhook", + "safeName": "webhook" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK", + "safeName": "WEBHOOK" + }, + "pascalCase": { + "unsafeName": "Webhook", + "safeName": "Webhook" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:Webhook" + }, + "shape": { + "_type": "object", + "extends": [ + { + "name": { + "originalName": "Declaration", + "camelCase": { + "unsafeName": "declaration", + "safeName": "declaration" + }, + "snakeCase": { + "unsafeName": "declaration", + "safeName": "declaration" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARATION", + "safeName": "DECLARATION" + }, + "pascalCase": { + "unsafeName": "Declaration", + "safeName": "Declaration" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Declaration" + } + ], + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "WebhookName", + "camelCase": { + "unsafeName": "webhookName", + "safeName": "webhookName" + }, + "snakeCase": { + "unsafeName": "webhook_name", + "safeName": "webhook_name" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK_NAME", + "safeName": "WEBHOOK_NAME" + }, + "pascalCase": { + "unsafeName": "WebhookName", + "safeName": "WebhookName" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:WebhookName" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "displayName", + "camelCase": { + "unsafeName": "displayName", + "safeName": "displayName" + }, + "snakeCase": { + "unsafeName": "display_name", + "safeName": "display_name" + }, + "screamingSnakeCase": { + "unsafeName": "DISPLAY_NAME", + "safeName": "DISPLAY_NAME" + }, + "pascalCase": { + "unsafeName": "DisplayName", + "safeName": "DisplayName" + } + }, + "wireValue": "displayName" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": "STRING" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "method", + "camelCase": { + "unsafeName": "method", + "safeName": "method" + }, + "snakeCase": { + "unsafeName": "method", + "safeName": "method" + }, + "screamingSnakeCase": { + "unsafeName": "METHOD", + "safeName": "METHOD" + }, + "pascalCase": { + "unsafeName": "Method", + "safeName": "Method" + } + }, + "wireValue": "method" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "WebhookHttpMethod", + "camelCase": { + "unsafeName": "webhookHttpMethod", + "safeName": "webhookHttpMethod" + }, + "snakeCase": { + "unsafeName": "webhook_http_method", + "safeName": "webhook_http_method" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK_HTTP_METHOD", + "safeName": "WEBHOOK_HTTP_METHOD" + }, + "pascalCase": { + "unsafeName": "WebhookHttpMethod", + "safeName": "WebhookHttpMethod" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:WebhookHttpMethod" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "headers", + "camelCase": { + "unsafeName": "headers", + "safeName": "headers" + }, + "snakeCase": { + "unsafeName": "headers", + "safeName": "headers" + }, + "screamingSnakeCase": { + "unsafeName": "HEADERS", + "safeName": "HEADERS" + }, + "pascalCase": { + "unsafeName": "Headers", + "safeName": "Headers" + } + }, + "wireValue": "headers" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "HttpHeader", + "camelCase": { + "unsafeName": "httpHeader", + "safeName": "httpHeader" + }, + "snakeCase": { + "unsafeName": "http_header", + "safeName": "http_header" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP_HEADER", + "safeName": "HTTP_HEADER" + }, + "pascalCase": { + "unsafeName": "HttpHeader", + "safeName": "HttpHeader" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Http", + "safeName": "Http" } } - }, - "typeId": "type_types:ExampleTypeReference" - } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:HttpHeader" } } }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "map", + "originalName": "payload", "camelCase": { - "unsafeName": "map", - "safeName": "map" + "unsafeName": "payload", + "safeName": "payload" }, "snakeCase": { - "unsafeName": "map", - "safeName": "map" + "unsafeName": "payload", + "safeName": "payload" }, "screamingSnakeCase": { - "unsafeName": "MAP", - "safeName": "MAP" + "unsafeName": "PAYLOAD", + "safeName": "PAYLOAD" }, "pascalCase": { - "unsafeName": "Map", - "safeName": "Map" + "unsafeName": "Payload", + "safeName": "Payload" } }, - "wireValue": "map" + "wireValue": "payload" }, - "shape": { - "_type": "singleProperty", + "valueType": { + "_type": "named", "name": { - "name": { - "originalName": "map", + "originalName": "WebhookPayload", + "camelCase": { + "unsafeName": "webhookPayload", + "safeName": "webhookPayload" + }, + "snakeCase": { + "unsafeName": "webhook_payload", + "safeName": "webhook_payload" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOK_PAYLOAD", + "safeName": "WEBHOOK_PAYLOAD" + }, + "pascalCase": { + "unsafeName": "WebhookPayload", + "safeName": "WebhookPayload" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", "camelCase": { - "unsafeName": "map", - "safeName": "map" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "map", - "safeName": "map" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "MAP", - "safeName": "MAP" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Map", - "safeName": "Map" - } - }, - "wireValue": "map" - }, - "type": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "ExampleKeyValuePair", - "camelCase": { - "unsafeName": "exampleKeyValuePair", - "safeName": "exampleKeyValuePair" - }, - "snakeCase": { - "unsafeName": "example_key_value_pair", - "safeName": "example_key_value_pair" - }, - "screamingSnakeCase": { - "unsafeName": "EXAMPLE_KEY_VALUE_PAIR", - "safeName": "EXAMPLE_KEY_VALUE_PAIR" - }, - "pascalCase": { - "unsafeName": "ExampleKeyValuePair", - "safeName": "ExampleKeyValuePair" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:ExampleKeyValuePair" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } - } + }, + "typeId": "type_webhooks:WebhookPayload" }, + "availability": null, "docs": null } ] }, "referencedTypes": [ - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_webhooks:WebhookName", "type_commons:Name", "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", + "type_webhooks:WebhookHttpMethod", + "type_http:HttpHeader", "type_commons:NameAndWireValue", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType", + "type_webhooks:WebhookPayload", + "type_webhooks:InlinedWebhookPayload", + "type_webhooks:InlinedWebhookPayloadProperty", + "type_webhooks:WebhookPayloadReference" ], "examples": [], "availability": null, "docs": null }, - "type_types:ExampleKeyValuePair": { + "type_webhooks:WebhookName": { "name": { "name": { - "originalName": "ExampleKeyValuePair", + "originalName": "WebhookName", "camelCase": { - "unsafeName": "exampleKeyValuePair", - "safeName": "exampleKeyValuePair" + "unsafeName": "webhookName", + "safeName": "webhookName" }, "snakeCase": { - "unsafeName": "example_key_value_pair", - "safeName": "example_key_value_pair" + "unsafeName": "webhook_name", + "safeName": "webhook_name" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_KEY_VALUE_PAIR", - "safeName": "EXAMPLE_KEY_VALUE_PAIR" + "unsafeName": "WEBHOOK_NAME", + "safeName": "WEBHOOK_NAME" }, "pascalCase": { - "unsafeName": "ExampleKeyValuePair", - "safeName": "ExampleKeyValuePair" + "unsafeName": "WebhookName", + "safeName": "WebhookName" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_types:ExampleKeyValuePair" + "typeId": "type_webhooks:WebhookName" }, "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "key", - "camelCase": { - "unsafeName": "key", - "safeName": "key" - }, - "snakeCase": { - "unsafeName": "key", - "safeName": "key" - }, - "screamingSnakeCase": { - "unsafeName": "KEY", - "safeName": "KEY" - }, - "pascalCase": { - "unsafeName": "Key", - "safeName": "Key" - } - }, - "wireValue": "key" + "_type": "alias", + "aliasOf": { + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" }, - "valueType": { - "_type": "named", - "name": { - "originalName": "ExampleTypeReference", - "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" - }, - "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" - }, - "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" - }, - "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - }, - "typeId": "type_types:ExampleTypeReference" + "snakeCase": { + "unsafeName": "name", + "safeName": "name" }, - "availability": null, - "docs": null + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } }, - { - "name": { - "name": { - "originalName": "value", + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", "camelCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "value", - "safeName": "value" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "VALUE", - "safeName": "VALUE" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Value", - "safeName": "Value" + "unsafeName": "Commons", + "safeName": "Commons" } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" }, - "wireValue": "value" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "ExampleTypeReference", - "camelCase": { - "unsafeName": "exampleTypeReference", - "safeName": "exampleTypeReference" - }, - "snakeCase": { - "unsafeName": "example_type_reference", - "safeName": "example_type_reference" - }, - "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_REFERENCE", - "safeName": "EXAMPLE_TYPE_REFERENCE" - }, - "pascalCase": { - "unsafeName": "ExampleTypeReference", - "safeName": "ExampleTypeReference" - } + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:Name" + }, + "resolvedType": { + "_type": "named", + "name": { + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } - }, - "typeId": "type_types:ExampleTypeReference" + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "availability": null, - "docs": null - } - ] + "typeId": "type_commons:Name" + }, + "shape": "OBJECT" + } }, "referencedTypes": [ - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_commons:SafeAndUnsafeString" ], "examples": [], "availability": null, "docs": null }, - "type_types:ExamplePrimitive": { + "type_webhooks:WebhookPayload": { "name": { "name": { - "originalName": "ExamplePrimitive", + "originalName": "WebhookPayload", "camelCase": { - "unsafeName": "examplePrimitive", - "safeName": "examplePrimitive" + "unsafeName": "webhookPayload", + "safeName": "webhookPayload" }, "snakeCase": { - "unsafeName": "example_primitive", - "safeName": "example_primitive" + "unsafeName": "webhook_payload", + "safeName": "webhook_payload" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_PRIMITIVE", - "safeName": "EXAMPLE_PRIMITIVE" + "unsafeName": "WEBHOOK_PAYLOAD", + "safeName": "WEBHOOK_PAYLOAD" }, "pascalCase": { - "unsafeName": "ExamplePrimitive", - "safeName": "ExamplePrimitive" + "unsafeName": "WebhookPayload", + "safeName": "WebhookPayload" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_types:ExamplePrimitive" + "typeId": "type_webhooks:WebhookPayload" }, "shape": { "_type": "union", @@ -40799,710 +45280,386 @@ { "discriminantValue": { "name": { - "originalName": "integer", - "camelCase": { - "unsafeName": "integer", - "safeName": "integer" - }, - "snakeCase": { - "unsafeName": "integer", - "safeName": "integer" - }, - "screamingSnakeCase": { - "unsafeName": "INTEGER", - "safeName": "INTEGER" - }, - "pascalCase": { - "unsafeName": "Integer", - "safeName": "Integer" - } - }, - "wireValue": "integer" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "integer", - "camelCase": { - "unsafeName": "integer", - "safeName": "integer" - }, - "snakeCase": { - "unsafeName": "integer", - "safeName": "integer" - }, - "screamingSnakeCase": { - "unsafeName": "INTEGER", - "safeName": "INTEGER" - }, - "pascalCase": { - "unsafeName": "Integer", - "safeName": "Integer" - } - }, - "wireValue": "integer" - }, - "type": { - "_type": "primitive", - "primitive": "INTEGER" - } - }, - "docs": null - }, - { - "discriminantValue": { - "name": { - "originalName": "double", + "originalName": "inlinedPayload", "camelCase": { - "unsafeName": "double", - "safeName": "double" + "unsafeName": "inlinedPayload", + "safeName": "inlinedPayload" }, "snakeCase": { - "unsafeName": "double", - "safeName": "double" + "unsafeName": "inlined_payload", + "safeName": "inlined_payload" }, "screamingSnakeCase": { - "unsafeName": "DOUBLE", - "safeName": "DOUBLE" + "unsafeName": "INLINED_PAYLOAD", + "safeName": "INLINED_PAYLOAD" }, "pascalCase": { - "unsafeName": "Double", - "safeName": "Double" + "unsafeName": "InlinedPayload", + "safeName": "InlinedPayload" } }, - "wireValue": "double" + "wireValue": "inlinedPayload" }, "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "double", - "camelCase": { - "unsafeName": "double", - "safeName": "double" - }, - "snakeCase": { - "unsafeName": "double", - "safeName": "double" - }, - "screamingSnakeCase": { - "unsafeName": "DOUBLE", - "safeName": "DOUBLE" - }, - "pascalCase": { - "unsafeName": "Double", - "safeName": "Double" - } - }, - "wireValue": "double" - }, - "type": { - "_type": "primitive", - "primitive": "DOUBLE" - } - }, - "docs": null - }, - { - "discriminantValue": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "string", + "originalName": "InlinedWebhookPayload", "camelCase": { - "unsafeName": "string", - "safeName": "string" + "unsafeName": "inlinedWebhookPayload", + "safeName": "inlinedWebhookPayload" }, "snakeCase": { - "unsafeName": "string", - "safeName": "string" + "unsafeName": "inlined_webhook_payload", + "safeName": "inlined_webhook_payload" }, "screamingSnakeCase": { - "unsafeName": "STRING", - "safeName": "STRING" + "unsafeName": "INLINED_WEBHOOK_PAYLOAD", + "safeName": "INLINED_WEBHOOK_PAYLOAD" }, "pascalCase": { - "unsafeName": "String", - "safeName": "String" + "unsafeName": "InlinedWebhookPayload", + "safeName": "InlinedWebhookPayload" } }, - "wireValue": "string" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "string", - "camelCase": { - "unsafeName": "string", - "safeName": "string" - }, - "snakeCase": { - "unsafeName": "string", - "safeName": "string" - }, - "screamingSnakeCase": { - "unsafeName": "STRING", - "safeName": "STRING" - }, - "pascalCase": { - "unsafeName": "String", - "safeName": "String" - } - }, - "wireValue": "string" - }, - "type": { - "_type": "named", - "name": { - "originalName": "EscapedString", - "camelCase": { - "unsafeName": "escapedString", - "safeName": "escapedString" - }, - "snakeCase": { - "unsafeName": "escaped_string", - "safeName": "escaped_string" - }, - "screamingSnakeCase": { - "unsafeName": "ESCAPED_STRING", - "safeName": "ESCAPED_STRING" - }, - "pascalCase": { - "unsafeName": "EscapedString", - "safeName": "EscapedString" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:EscapedString" - } - }, - "docs": null - }, - { - "discriminantValue": { - "name": { - "originalName": "boolean", - "camelCase": { - "unsafeName": "boolean", - "safeName": "boolean" - }, - "snakeCase": { - "unsafeName": "boolean", - "safeName": "boolean" - }, - "screamingSnakeCase": { - "unsafeName": "BOOLEAN", - "safeName": "BOOLEAN" - }, - "pascalCase": { - "unsafeName": "Boolean", - "safeName": "Boolean" - } - }, - "wireValue": "boolean" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "boolean", - "camelCase": { - "unsafeName": "boolean", - "safeName": "boolean" - }, - "snakeCase": { - "unsafeName": "boolean", - "safeName": "boolean" - }, - "screamingSnakeCase": { - "unsafeName": "BOOLEAN", - "safeName": "BOOLEAN" - }, - "pascalCase": { - "unsafeName": "Boolean", - "safeName": "Boolean" - } - }, - "wireValue": "boolean" - }, - "type": { - "_type": "primitive", - "primitive": "BOOLEAN" - } - }, - "docs": null - }, - { - "discriminantValue": { - "name": { - "originalName": "long", - "camelCase": { - "unsafeName": "long", - "safeName": "long" - }, - "snakeCase": { - "unsafeName": "long", - "safeName": "long" - }, - "screamingSnakeCase": { - "unsafeName": "LONG", - "safeName": "LONG" - }, - "pascalCase": { - "unsafeName": "Long", - "safeName": "Long" - } - }, - "wireValue": "long" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "long", - "camelCase": { - "unsafeName": "long", - "safeName": "long" - }, - "snakeCase": { - "unsafeName": "long", - "safeName": "long" - }, - "screamingSnakeCase": { - "unsafeName": "LONG", - "safeName": "LONG" - }, - "pascalCase": { - "unsafeName": "Long", - "safeName": "Long" - } - }, - "wireValue": "long" - }, - "type": { - "_type": "primitive", - "primitive": "LONG" - } - }, - "docs": null - }, - { - "discriminantValue": { - "name": { - "originalName": "datetime", - "camelCase": { - "unsafeName": "datetime", - "safeName": "datetime" - }, - "snakeCase": { - "unsafeName": "datetime", - "safeName": "datetime" - }, - "screamingSnakeCase": { - "unsafeName": "DATETIME", - "safeName": "DATETIME" - }, - "pascalCase": { - "unsafeName": "Datetime", - "safeName": "Datetime" - } - }, - "wireValue": "datetime" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "datetime", + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", "camelCase": { - "unsafeName": "datetime", - "safeName": "datetime" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "datetime", - "safeName": "datetime" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "DATETIME", - "safeName": "DATETIME" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Datetime", - "safeName": "Datetime" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } - }, - "wireValue": "datetime" + } }, - "type": { - "_type": "primitive", - "primitive": "DATE_TIME" - } + "typeId": "type_webhooks:InlinedWebhookPayload" }, "docs": null }, { "discriminantValue": { "name": { - "originalName": "date", + "originalName": "reference", "camelCase": { - "unsafeName": "date", - "safeName": "date" + "unsafeName": "reference", + "safeName": "reference" }, "snakeCase": { - "unsafeName": "date", - "safeName": "date" + "unsafeName": "reference", + "safeName": "reference" }, "screamingSnakeCase": { - "unsafeName": "DATE", - "safeName": "DATE" + "unsafeName": "REFERENCE", + "safeName": "REFERENCE" }, "pascalCase": { - "unsafeName": "Date", - "safeName": "Date" + "unsafeName": "Reference", + "safeName": "Reference" } }, - "wireValue": "date" + "wireValue": "reference" }, "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "date", - "camelCase": { - "unsafeName": "date", - "safeName": "date" - }, - "snakeCase": { - "unsafeName": "date", - "safeName": "date" - }, - "screamingSnakeCase": { - "unsafeName": "DATE", - "safeName": "DATE" - }, - "pascalCase": { - "unsafeName": "Date", - "safeName": "Date" - } - }, - "wireValue": "date" - }, - "type": { - "_type": "primitive", - "primitive": "DATE" - } - }, - "docs": null - }, - { - "discriminantValue": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "uuid", + "originalName": "WebhookPayloadReference", "camelCase": { - "unsafeName": "uuid", - "safeName": "uuid" + "unsafeName": "webhookPayloadReference", + "safeName": "webhookPayloadReference" }, "snakeCase": { - "unsafeName": "uuid", - "safeName": "uuid" + "unsafeName": "webhook_payload_reference", + "safeName": "webhook_payload_reference" }, "screamingSnakeCase": { - "unsafeName": "UUID", - "safeName": "UUID" + "unsafeName": "WEBHOOK_PAYLOAD_REFERENCE", + "safeName": "WEBHOOK_PAYLOAD_REFERENCE" }, "pascalCase": { - "unsafeName": "Uuid", - "safeName": "Uuid" + "unsafeName": "WebhookPayloadReference", + "safeName": "WebhookPayloadReference" } }, - "wireValue": "uuid" - }, - "shape": { - "_type": "singleProperty", - "name": { - "name": { - "originalName": "uuid", + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", "camelCase": { - "unsafeName": "uuid", - "safeName": "uuid" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "uuid", - "safeName": "uuid" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "UUID", - "safeName": "UUID" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Uuid", - "safeName": "Uuid" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } - }, - "wireValue": "uuid" + } }, - "type": { - "_type": "primitive", - "primitive": "UUID" - } + "typeId": "type_webhooks:WebhookPayloadReference" }, "docs": null } ] }, "referencedTypes": [ - "type_commons:EscapedString" + "type_webhooks:InlinedWebhookPayload", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_webhooks:InlinedWebhookPayloadProperty", + "type_commons:WithDocs", + "type_commons:NameAndWireValue", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:PrimitiveType", + "type_webhooks:WebhookPayloadReference" ], "examples": [], "availability": null, "docs": null }, - "type_types:ExampleNamedType": { + "type_webhooks:WebhookPayloadReference": { "name": { "name": { - "originalName": "ExampleNamedType", + "originalName": "WebhookPayloadReference", "camelCase": { - "unsafeName": "exampleNamedType", - "safeName": "exampleNamedType" + "unsafeName": "webhookPayloadReference", + "safeName": "webhookPayloadReference" }, "snakeCase": { - "unsafeName": "example_named_type", - "safeName": "example_named_type" + "unsafeName": "webhook_payload_reference", + "safeName": "webhook_payload_reference" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_NAMED_TYPE", - "safeName": "EXAMPLE_NAMED_TYPE" + "unsafeName": "WEBHOOK_PAYLOAD_REFERENCE", + "safeName": "WEBHOOK_PAYLOAD_REFERENCE" }, "pascalCase": { - "unsafeName": "ExampleNamedType", - "safeName": "ExampleNamedType" + "unsafeName": "WebhookPayloadReference", + "safeName": "WebhookPayloadReference" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_types:ExampleNamedType" + "typeId": "type_webhooks:WebhookPayloadReference" }, "shape": { "_type": "object", - "extends": [], - "properties": [ + "extends": [ { "name": { - "name": { - "originalName": "typeName", - "camelCase": { - "unsafeName": "typeName", - "safeName": "typeName" - }, - "snakeCase": { - "unsafeName": "type_name", - "safeName": "type_name" - }, - "screamingSnakeCase": { - "unsafeName": "TYPE_NAME", - "safeName": "TYPE_NAME" - }, - "pascalCase": { - "unsafeName": "TypeName", - "safeName": "TypeName" - } + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" }, - "wireValue": "typeName" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "DeclaredTypeName", - "camelCase": { - "unsafeName": "declaredTypeName", - "safeName": "declaredTypeName" - }, - "snakeCase": { - "unsafeName": "declared_type_name", - "safeName": "declared_type_name" - }, - "screamingSnakeCase": { - "unsafeName": "DECLARED_TYPE_NAME", - "safeName": "DECLARED_TYPE_NAME" - }, - "pascalCase": { - "unsafeName": "DeclaredTypeName", - "safeName": "DeclaredTypeName" - } + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" - }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" - }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } - } - ], - "packagePath": [], - "file": { - "originalName": "types", + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Commons", + "safeName": "Commons" } } - }, - "typeId": "type_types:DeclaredTypeName" + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "availability": null, - "docs": null - }, + "typeId": "type_commons:WithDocs" + } + ], + "properties": [ { "name": { "name": { - "originalName": "shape", + "originalName": "payloadType", "camelCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "payloadType", + "safeName": "payloadType" }, "snakeCase": { - "unsafeName": "shape", - "safeName": "shape" + "unsafeName": "payload_type", + "safeName": "payload_type" }, "screamingSnakeCase": { - "unsafeName": "SHAPE", - "safeName": "SHAPE" + "unsafeName": "PAYLOAD_TYPE", + "safeName": "PAYLOAD_TYPE" }, "pascalCase": { - "unsafeName": "Shape", - "safeName": "Shape" + "unsafeName": "PayloadType", + "safeName": "PayloadType" } }, - "wireValue": "shape" + "wireValue": "payloadType" }, "valueType": { "_type": "named", "name": { - "originalName": "ExampleTypeShape", + "originalName": "TypeReference", "camelCase": { - "unsafeName": "exampleTypeShape", - "safeName": "exampleTypeShape" + "unsafeName": "typeReference", + "safeName": "typeReference" }, "snakeCase": { - "unsafeName": "example_type_shape", - "safeName": "example_type_shape" + "unsafeName": "type_reference", + "safeName": "type_reference" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_TYPE_SHAPE", - "safeName": "EXAMPLE_TYPE_SHAPE" + "unsafeName": "TYPE_REFERENCE", + "safeName": "TYPE_REFERENCE" }, "pascalCase": { - "unsafeName": "ExampleTypeShape", - "safeName": "ExampleTypeShape" + "unsafeName": "TypeReference", + "safeName": "TypeReference" } }, "fernFilepath": { @@ -41548,7 +45705,7 @@ } } }, - "typeId": "type_types:ExampleTypeShape" + "typeId": "type_types:TypeReference" }, "availability": null, "docs": null @@ -41556,100 +45713,87 @@ ] }, "referencedTypes": [ + "type_commons:WithDocs", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", "type_commons:Name", "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", - "type_types:ExampleEnumType", - "type_commons:NameAndWireValue", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_types:PrimitiveType" ], "examples": [], "availability": null, "docs": null }, - "type_types:ExampleObjectTypeWithTypeId": { + "type_webhooks:InlinedWebhookPayload": { "name": { "name": { - "originalName": "ExampleObjectTypeWithTypeId", + "originalName": "InlinedWebhookPayload", "camelCase": { - "unsafeName": "exampleObjectTypeWithTypeId", - "safeName": "exampleObjectTypeWithTypeId" + "unsafeName": "inlinedWebhookPayload", + "safeName": "inlinedWebhookPayload" }, "snakeCase": { - "unsafeName": "example_object_type_with_type_id", - "safeName": "example_object_type_with_type_id" + "unsafeName": "inlined_webhook_payload", + "safeName": "inlined_webhook_payload" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_OBJECT_TYPE_WITH_TYPE_ID", - "safeName": "EXAMPLE_OBJECT_TYPE_WITH_TYPE_ID" + "unsafeName": "INLINED_WEBHOOK_PAYLOAD", + "safeName": "INLINED_WEBHOOK_PAYLOAD" }, "pascalCase": { - "unsafeName": "ExampleObjectTypeWithTypeId", - "safeName": "ExampleObjectTypeWithTypeId" + "unsafeName": "InlinedWebhookPayload", + "safeName": "InlinedWebhookPayload" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "webhooks", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_types:ExampleObjectTypeWithTypeId" + "typeId": "type_webhooks:InlinedWebhookPayload" }, "shape": { "_type": "object", @@ -41658,45 +45802,45 @@ { "name": { "name": { - "originalName": "typeId", + "originalName": "name", "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" + "unsafeName": "Name", + "safeName": "Name" } }, - "wireValue": "typeId" + "wireValue": "name" }, "valueType": { "_type": "named", "name": { - "originalName": "TypeId", + "originalName": "Name", "camelCase": { - "unsafeName": "typeId", - "safeName": "typeId" + "unsafeName": "name", + "safeName": "name" }, "snakeCase": { - "unsafeName": "type_id", - "safeName": "type_id" + "unsafeName": "name", + "safeName": "name" }, "screamingSnakeCase": { - "unsafeName": "TYPE_ID", - "safeName": "TYPE_ID" + "unsafeName": "NAME", + "safeName": "NAME" }, "pascalCase": { - "unsafeName": "TypeId", - "safeName": "TypeId" + "unsafeName": "Name", + "safeName": "Name" } }, "fernFilepath": { @@ -41742,7 +45886,7 @@ } } }, - "typeId": "type_commons:TypeId" + "typeId": "type_commons:Name" }, "availability": null, "docs": null @@ -41750,91 +45894,195 @@ { "name": { "name": { - "originalName": "object", + "originalName": "extends", "camelCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "extends", + "safeName": "extends" }, "snakeCase": { - "unsafeName": "object", - "safeName": "object" + "unsafeName": "extends", + "safeName": "extends" }, "screamingSnakeCase": { - "unsafeName": "OBJECT", - "safeName": "OBJECT" + "unsafeName": "EXTENDS", + "safeName": "EXTENDS" }, "pascalCase": { - "unsafeName": "Object", - "safeName": "Object" + "unsafeName": "Extends", + "safeName": "Extends" } }, - "wireValue": "object" + "wireValue": "extends" }, "valueType": { - "_type": "named", + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "DeclaredTypeName", + "camelCase": { + "unsafeName": "declaredTypeName", + "safeName": "declaredTypeName" + }, + "snakeCase": { + "unsafeName": "declared_type_name", + "safeName": "declared_type_name" + }, + "screamingSnakeCase": { + "unsafeName": "DECLARED_TYPE_NAME", + "safeName": "DECLARED_TYPE_NAME" + }, + "pascalCase": { + "unsafeName": "DeclaredTypeName", + "safeName": "DeclaredTypeName" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:DeclaredTypeName" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { "name": { - "originalName": "ExampleObjectType", + "originalName": "properties", "camelCase": { - "unsafeName": "exampleObjectType", - "safeName": "exampleObjectType" + "unsafeName": "properties", + "safeName": "properties" }, "snakeCase": { - "unsafeName": "example_object_type", - "safeName": "example_object_type" + "unsafeName": "properties", + "safeName": "properties" }, "screamingSnakeCase": { - "unsafeName": "EXAMPLE_OBJECT_TYPE", - "safeName": "EXAMPLE_OBJECT_TYPE" + "unsafeName": "PROPERTIES", + "safeName": "PROPERTIES" }, "pascalCase": { - "unsafeName": "ExampleObjectType", - "safeName": "ExampleObjectType" + "unsafeName": "Properties", + "safeName": "Properties" } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "types", + "wireValue": "properties" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "InlinedWebhookPayloadProperty", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "inlinedWebhookPayloadProperty", + "safeName": "inlinedWebhookPayloadProperty" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "inlined_webhook_payload_property", + "safeName": "inlined_webhook_payload_property" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY", + "safeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "InlinedWebhookPayloadProperty", + "safeName": "InlinedWebhookPayloadProperty" } - } - ], - "packagePath": [], - "file": { - "originalName": "types", - "camelCase": { - "unsafeName": "types", - "safeName": "types" - }, - "snakeCase": { - "unsafeName": "types", - "safeName": "types" }, - "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } }, - "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" - } + "typeId": "type_webhooks:InlinedWebhookPayloadProperty" } - }, - "typeId": "type_types:ExampleObjectType" + } }, "availability": null, "docs": null @@ -41842,182 +46090,89 @@ ] }, "referencedTypes": [ - "type_commons:TypeId", - "type_types:ExampleObjectType", - "type_types:ExampleObjectProperty", - "type_commons:NameAndWireValue", "type_commons:Name", "type_commons:SafeAndUnsafeString", - "type_types:ExampleTypeReference", - "type_commons:WithJsonExample", - "type_types:ExampleTypeReferenceShape", - "type_types:ExamplePrimitive", - "type_commons:EscapedString", - "type_types:ExampleContainer", - "type_types:ExampleKeyValuePair", - "type_types:ExampleNamedType", "type_types:DeclaredTypeName", + "type_commons:TypeId", "type_commons:FernFilepath", - "type_types:ExampleTypeShape", - "type_types:ExampleAliasType", - "type_types:ExampleEnumType", - "type_types:ExampleUnionType", - "type_types:ExampleSingleUnionType", - "type_types:ExampleSingleUnionTypeProperties", - "type_types:ExampleObjectTypeWithTypeId", - "type_types:ExampleUndiscriminatedUnionType" + "type_webhooks:InlinedWebhookPayloadProperty", + "type_commons:WithDocs", + "type_commons:NameAndWireValue", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:PrimitiveType" ], "examples": [], "availability": null, "docs": null }, - "type_variables:VariableId": { - "name": { - "name": { - "originalName": "VariableId", - "camelCase": { - "unsafeName": "variableId", - "safeName": "variableId" - }, - "snakeCase": { - "unsafeName": "variable_id", - "safeName": "variable_id" - }, - "screamingSnakeCase": { - "unsafeName": "VARIABLE_ID", - "safeName": "VARIABLE_ID" - }, - "pascalCase": { - "unsafeName": "VariableId", - "safeName": "VariableId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "variables", - "camelCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" - }, - "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" - } - } - ], - "packagePath": [], - "file": { - "originalName": "variables", - "camelCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" - }, - "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" - } - } - }, - "typeId": "type_variables:VariableId" - }, - "shape": { - "_type": "alias", - "aliasOf": { - "_type": "primitive", - "primitive": "STRING" - }, - "resolvedType": { - "_type": "primitive", - "primitive": "STRING" - } - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_variables:VariableDeclaration": { + "type_webhooks:InlinedWebhookPayloadProperty": { "name": { "name": { - "originalName": "VariableDeclaration", + "originalName": "InlinedWebhookPayloadProperty", "camelCase": { - "unsafeName": "variableDeclaration", - "safeName": "variableDeclaration" + "unsafeName": "inlinedWebhookPayloadProperty", + "safeName": "inlinedWebhookPayloadProperty" }, "snakeCase": { - "unsafeName": "variable_declaration", - "safeName": "variable_declaration" + "unsafeName": "inlined_webhook_payload_property", + "safeName": "inlined_webhook_payload_property" }, "screamingSnakeCase": { - "unsafeName": "VARIABLE_DECLARATION", - "safeName": "VARIABLE_DECLARATION" + "unsafeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY", + "safeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY" }, "pascalCase": { - "unsafeName": "VariableDeclaration", - "safeName": "VariableDeclaration" + "unsafeName": "InlinedWebhookPayloadProperty", + "safeName": "InlinedWebhookPayloadProperty" } }, "fernFilepath": { "allParts": [ { - "originalName": "variables", + "originalName": "webhooks", "camelCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } ], "packagePath": [], "file": { - "originalName": "variables", + "originalName": "webhooks", "camelCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "unsafeName": "webhooks", + "safeName": "webhooks" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Webhooks", + "safeName": "Webhooks" } } }, - "typeId": "type_variables:VariableDeclaration" + "typeId": "type_webhooks:InlinedWebhookPayloadProperty" }, "shape": { "_type": "object", @@ -42050,137 +46205,45 @@ "unsafeName": "commons", "safeName": "commons" }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:WithDocs" - } - ], - "properties": [ - { - "name": { - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "Id", - "safeName": "Id" - } - }, - "wireValue": "id" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "VariableId", - "camelCase": { - "unsafeName": "variableId", - "safeName": "variableId" - }, - "snakeCase": { - "unsafeName": "variable_id", - "safeName": "variable_id" - }, - "screamingSnakeCase": { - "unsafeName": "VARIABLE_ID", - "safeName": "VARIABLE_ID" - }, - "pascalCase": { - "unsafeName": "VariableId", - "safeName": "VariableId" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "variables", - "camelCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" - }, - "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" - } - } - ], - "packagePath": [], - "file": { - "originalName": "variables", - "camelCase": { - "unsafeName": "variables", - "safeName": "variables" - }, - "snakeCase": { - "unsafeName": "variables", - "safeName": "variables" + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" }, "screamingSnakeCase": { - "unsafeName": "VARIABLES", - "safeName": "VARIABLES" + "unsafeName": "COMMONS", + "safeName": "COMMONS" }, "pascalCase": { - "unsafeName": "Variables", - "safeName": "Variables" + "unsafeName": "Commons", + "safeName": "Commons" } } - }, - "typeId": "type_variables:VariableId" + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } }, - "availability": null, - "docs": null - }, + "typeId": "type_commons:WithDocs" + } + ], + "properties": [ { "name": { "name": { @@ -42207,22 +46270,22 @@ "valueType": { "_type": "named", "name": { - "originalName": "Name", + "originalName": "NameAndWireValue", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" } }, "fernFilepath": { @@ -42268,7 +46331,7 @@ } } }, - "typeId": "type_commons:Name" + "typeId": "type_commons:NameAndWireValue" }, "availability": null, "docs": null @@ -42276,25 +46339,25 @@ { "name": { "name": { - "originalName": "type", + "originalName": "valueType", "camelCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "valueType", + "safeName": "valueType" }, "snakeCase": { - "unsafeName": "type", - "safeName": "type" + "unsafeName": "value_type", + "safeName": "value_type" }, "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" + "unsafeName": "VALUE_TYPE", + "safeName": "VALUE_TYPE" }, "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" + "unsafeName": "ValueType", + "safeName": "ValueType" } }, - "wireValue": "type" + "wireValue": "valueType" }, "valueType": { "_type": "named", @@ -42369,7 +46432,7 @@ }, "referencedTypes": [ "type_commons:WithDocs", - "type_variables:VariableId", + "type_commons:NameAndWireValue", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:TypeReference", @@ -42385,25 +46448,25 @@ "availability": null, "docs": null }, - "type_webhooks:WebhookGroup": { + "type_webhooks:WebhookHttpMethod": { "name": { "name": { - "originalName": "WebhookGroup", + "originalName": "WebhookHttpMethod", "camelCase": { - "unsafeName": "webhookGroup", - "safeName": "webhookGroup" + "unsafeName": "webhookHttpMethod", + "safeName": "webhookHttpMethod" }, "snakeCase": { - "unsafeName": "webhook_group", - "safeName": "webhook_group" + "unsafeName": "webhook_http_method", + "safeName": "webhook_http_method" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_GROUP", - "safeName": "WEBHOOK_GROUP" + "unsafeName": "WEBHOOK_HTTP_METHOD", + "safeName": "WEBHOOK_HTTP_METHOD" }, "pascalCase": { - "unsafeName": "WebhookGroup", - "safeName": "WebhookGroup" + "unsafeName": "WebhookHttpMethod", + "safeName": "WebhookHttpMethod" } }, "fernFilepath": { @@ -42449,249 +46512,217 @@ } } }, - "typeId": "type_webhooks:WebhookGroup" + "typeId": "type_webhooks:WebhookHttpMethod" }, "shape": { - "_type": "alias", - "aliasOf": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", + "_type": "enum", + "values": [ + { + "name": { "name": { - "originalName": "Webhook", + "originalName": "GET", "camelCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "get", + "safeName": "get" }, "snakeCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "get", + "safeName": "get" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK", - "safeName": "WEBHOOK" + "unsafeName": "GET", + "safeName": "GET" }, "pascalCase": { - "unsafeName": "Webhook", - "safeName": "Webhook" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } + "unsafeName": "Get", + "safeName": "Get" } }, - "typeId": "type_webhooks:Webhook" - } - } - }, - "resolvedType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", + "wireValue": "GET" + }, + "availability": null, + "docs": null + }, + { + "name": { "name": { - "originalName": "Webhook", + "originalName": "POST", "camelCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "post", + "safeName": "post" }, "snakeCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "post", + "safeName": "post" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK", - "safeName": "WEBHOOK" + "unsafeName": "POST", + "safeName": "POST" }, "pascalCase": { - "unsafeName": "Webhook", - "safeName": "Webhook" + "unsafeName": "Post", + "safeName": "Post" } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } + "wireValue": "POST" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, + "type_websocket:WebsocketMessageId": { + "name": { + "name": { + "originalName": "WebsocketMessageId", + "camelCase": { + "unsafeName": "websocketMessageId", + "safeName": "websocketMessageId" + }, + "snakeCase": { + "unsafeName": "websocket_message_id", + "safeName": "websocket_message_id" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE_ID", + "safeName": "WEBSOCKET_MESSAGE_ID" + }, + "pascalCase": { + "unsafeName": "WebsocketMessageId", + "safeName": "WebsocketMessageId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" }, - "typeId": "type_webhooks:Webhook" + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" } } + }, + "typeId": "type_websocket:WebsocketMessageId" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": "STRING" + }, + "resolvedType": { + "_type": "primitive", + "primitive": "STRING" } }, - "referencedTypes": [ - "type_webhooks:Webhook", - "type_commons:Declaration", - "type_commons:WithDocs", - "type_commons:Availability", - "type_commons:AvailabilityStatus", - "type_webhooks:WebhookName", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_webhooks:WebhookHttpMethod", - "type_http:HttpHeader", - "type_commons:NameAndWireValue", - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:PrimitiveType", - "type_webhooks:WebhookPayload", - "type_webhooks:InlinedWebhookPayload", - "type_webhooks:InlinedWebhookPayloadProperty", - "type_webhooks:WebhookPayloadReference" - ], + "referencedTypes": [], "examples": [], "availability": null, "docs": null }, - "type_webhooks:Webhook": { + "type_websocket:WebsocketChannel": { "name": { "name": { - "originalName": "Webhook", + "originalName": "WebsocketChannel", "camelCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "websocketChannel", + "safeName": "websocketChannel" }, "snakeCase": { - "unsafeName": "webhook", - "safeName": "webhook" + "unsafeName": "websocket_channel", + "safeName": "websocket_channel" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK", - "safeName": "WEBHOOK" + "unsafeName": "WEBSOCKET_CHANNEL", + "safeName": "WEBSOCKET_CHANNEL" }, "pascalCase": { - "unsafeName": "Webhook", - "safeName": "Webhook" + "unsafeName": "WebsocketChannel", + "safeName": "WebsocketChannel" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:Webhook" + "typeId": "type_websocket:WebsocketChannel" }, "shape": { "_type": "object", @@ -42766,219 +46797,121 @@ { "name": { "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - }, - "wireValue": "name" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "WebhookName", - "camelCase": { - "unsafeName": "webhookName", - "safeName": "webhookName" - }, - "snakeCase": { - "unsafeName": "webhook_name", - "safeName": "webhook_name" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOK_NAME", - "safeName": "WEBHOOK_NAME" - }, - "pascalCase": { - "unsafeName": "WebhookName", - "safeName": "WebhookName" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - }, - "typeId": "type_webhooks:WebhookName" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "displayName", - "camelCase": { - "unsafeName": "displayName", - "safeName": "displayName" - }, - "snakeCase": { - "unsafeName": "display_name", - "safeName": "display_name" - }, - "screamingSnakeCase": { - "unsafeName": "DISPLAY_NAME", - "safeName": "DISPLAY_NAME" - }, - "pascalCase": { - "unsafeName": "DisplayName", - "safeName": "DisplayName" - } - }, - "wireValue": "displayName" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "method", + "originalName": "path", "camelCase": { - "unsafeName": "method", - "safeName": "method" + "unsafeName": "path", + "safeName": "path" }, "snakeCase": { - "unsafeName": "method", - "safeName": "method" + "unsafeName": "path", + "safeName": "path" }, "screamingSnakeCase": { - "unsafeName": "METHOD", - "safeName": "METHOD" + "unsafeName": "PATH", + "safeName": "PATH" }, "pascalCase": { - "unsafeName": "Method", - "safeName": "Method" + "unsafeName": "Path", + "safeName": "Path" } }, - "wireValue": "method" + "wireValue": "path" }, "valueType": { "_type": "named", "name": { - "originalName": "WebhookHttpMethod", + "originalName": "HttpPath", "camelCase": { - "unsafeName": "webhookHttpMethod", - "safeName": "webhookHttpMethod" + "unsafeName": "httpPath", + "safeName": "httpPath" }, "snakeCase": { - "unsafeName": "webhook_http_method", - "safeName": "webhook_http_method" + "unsafeName": "http_path", + "safeName": "http_path" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_HTTP_METHOD", - "safeName": "WEBHOOK_HTTP_METHOD" + "unsafeName": "HTTP_PATH", + "safeName": "HTTP_PATH" }, "pascalCase": { - "unsafeName": "WebhookHttpMethod", - "safeName": "WebhookHttpMethod" + "unsafeName": "HttpPath", + "safeName": "HttpPath" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "http", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Http", + "safeName": "Http" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "http", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "http", + "safeName": "http" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "HTTP", + "safeName": "HTTP" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Http", + "safeName": "Http" } } }, - "typeId": "type_webhooks:WebhookHttpMethod" + "typeId": "type_http:HttpPath" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "auth", + "camelCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "snakeCase": { + "unsafeName": "auth", + "safeName": "auth" + }, + "screamingSnakeCase": { + "unsafeName": "AUTH", + "safeName": "AUTH" + }, + "pascalCase": { + "unsafeName": "Auth", + "safeName": "Auth" + } + }, + "wireValue": "auth" + }, + "valueType": { + "_type": "primitive", + "primitive": "BOOLEAN" }, "availability": null, "docs": null @@ -43084,279 +47017,487 @@ { "name": { "name": { - "originalName": "payload", + "originalName": "queryParameters", "camelCase": { - "unsafeName": "payload", - "safeName": "payload" + "unsafeName": "queryParameters", + "safeName": "queryParameters" }, "snakeCase": { - "unsafeName": "payload", - "safeName": "payload" + "unsafeName": "query_parameters", + "safeName": "query_parameters" }, "screamingSnakeCase": { - "unsafeName": "PAYLOAD", - "safeName": "PAYLOAD" + "unsafeName": "QUERY_PARAMETERS", + "safeName": "QUERY_PARAMETERS" }, "pascalCase": { - "unsafeName": "Payload", - "safeName": "Payload" + "unsafeName": "QueryParameters", + "safeName": "QueryParameters" } }, - "wireValue": "payload" + "wireValue": "queryParameters" }, "valueType": { - "_type": "named", + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "QueryParameter", + "camelCase": { + "unsafeName": "queryParameter", + "safeName": "queryParameter" + }, + "snakeCase": { + "unsafeName": "query_parameter", + "safeName": "query_parameter" + }, + "screamingSnakeCase": { + "unsafeName": "QUERY_PARAMETER", + "safeName": "QUERY_PARAMETER" + }, + "pascalCase": { + "unsafeName": "QueryParameter", + "safeName": "QueryParameter" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + }, + "typeId": "type_http:QueryParameter" + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { "name": { - "originalName": "WebhookPayload", + "originalName": "pathParameters", "camelCase": { - "unsafeName": "webhookPayload", - "safeName": "webhookPayload" + "unsafeName": "pathParameters", + "safeName": "pathParameters" }, "snakeCase": { - "unsafeName": "webhook_payload", - "safeName": "webhook_payload" + "unsafeName": "path_parameters", + "safeName": "path_parameters" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_PAYLOAD", - "safeName": "WEBHOOK_PAYLOAD" + "unsafeName": "PATH_PARAMETERS", + "safeName": "PATH_PARAMETERS" }, "pascalCase": { - "unsafeName": "WebhookPayload", - "safeName": "WebhookPayload" + "unsafeName": "PathParameters", + "safeName": "PathParameters" } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", + "wireValue": "pathParameters" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "PathParameter", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "pathParameter", + "safeName": "pathParameter" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "path_parameter", + "safeName": "path_parameter" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "PATH_PARAMETER", + "safeName": "PATH_PARAMETER" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "PathParameter", + "safeName": "PathParameter" } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "fernFilepath": { + "allParts": [ + { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } + ], + "packagePath": [], + "file": { + "originalName": "http", + "camelCase": { + "unsafeName": "http", + "safeName": "http" + }, + "snakeCase": { + "unsafeName": "http", + "safeName": "http" + }, + "screamingSnakeCase": { + "unsafeName": "HTTP", + "safeName": "HTTP" + }, + "pascalCase": { + "unsafeName": "Http", + "safeName": "Http" + } + } }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } + "typeId": "type_http:PathParameter" } - }, - "typeId": "type_webhooks:WebhookPayload" + } }, "availability": null, "docs": null - } - ] - }, - "referencedTypes": [ - "type_commons:Declaration", - "type_commons:WithDocs", - "type_commons:Availability", - "type_commons:AvailabilityStatus", - "type_webhooks:WebhookName", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_webhooks:WebhookHttpMethod", - "type_http:HttpHeader", - "type_commons:NameAndWireValue", - "type_types:TypeReference", - "type_types:ContainerType", - "type_types:MapType", - "type_types:Literal", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_types:PrimitiveType", - "type_webhooks:WebhookPayload", - "type_webhooks:InlinedWebhookPayload", - "type_webhooks:InlinedWebhookPayloadProperty", - "type_webhooks:WebhookPayloadReference" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_webhooks:WebhookName": { - "name": { - "name": { - "originalName": "WebhookName", - "camelCase": { - "unsafeName": "webhookName", - "safeName": "webhookName" - }, - "snakeCase": { - "unsafeName": "webhook_name", - "safeName": "webhook_name" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOK_NAME", - "safeName": "WEBHOOK_NAME" - }, - "pascalCase": { - "unsafeName": "WebhookName", - "safeName": "WebhookName" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - }, - "typeId": "type_webhooks:WebhookName" - }, - "shape": { - "_type": "alias", - "aliasOf": { - "_type": "named", - "name": { - "originalName": "Name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", + { + "name": { + "name": { + "originalName": "messages", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "messages", + "safeName": "messages" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "messages", + "safeName": "messages" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "MESSAGES", + "safeName": "MESSAGES" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Messages", + "safeName": "Messages" + } + }, + "wireValue": "messages" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "named", + "name": { + "originalName": "WebsocketMessageId", + "camelCase": { + "unsafeName": "websocketMessageId", + "safeName": "websocketMessageId" + }, + "snakeCase": { + "unsafeName": "websocket_message_id", + "safeName": "websocket_message_id" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE_ID", + "safeName": "WEBSOCKET_MESSAGE_ID" + }, + "pascalCase": { + "unsafeName": "WebsocketMessageId", + "safeName": "WebsocketMessageId" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "typeId": "type_websocket:WebsocketMessageId" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "WebsocketMessage", + "camelCase": { + "unsafeName": "websocketMessage", + "safeName": "websocketMessage" + }, + "snakeCase": { + "unsafeName": "websocket_message", + "safeName": "websocket_message" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE", + "safeName": "WEBSOCKET_MESSAGE" + }, + "pascalCase": { + "unsafeName": "WebsocketMessage", + "safeName": "WebsocketMessage" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "typeId": "type_websocket:WebsocketMessage" } } - ], - "packagePath": [], - "file": { - "originalName": "commons", + }, + "availability": null, + "docs": "The messages that can be sent and received on this channel" + } + ] + }, + "referencedTypes": [ + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_http:HttpPath", + "type_http:HttpPathPart", + "type_http:HttpHeader", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType", + "type_http:QueryParameter", + "type_http:PathParameter", + "type_http:PathParameterLocation", + "type_variables:VariableId", + "type_websocket:WebsocketMessageId", + "type_websocket:WebsocketMessage", + "type_websocket:WebsocketMessageOrigin", + "type_websocket:WebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBodyProperty", + "type_websocket:WebsocketMessageBodyReference" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_websocket:WebsocketMessage": { + "name": { + "name": { + "originalName": "WebsocketMessage", + "camelCase": { + "unsafeName": "websocketMessage", + "safeName": "websocketMessage" + }, + "snakeCase": { + "unsafeName": "websocket_message", + "safeName": "websocket_message" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE", + "safeName": "WEBSOCKET_MESSAGE" + }, + "pascalCase": { + "unsafeName": "WebsocketMessage", + "safeName": "WebsocketMessage" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Websocket", + "safeName": "Websocket" } } - }, - "typeId": "type_commons:Name" + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } }, - "resolvedType": { - "_type": "named", - "name": { + "typeId": "type_websocket:WebsocketMessage" + }, + "shape": { + "_type": "object", + "extends": [ + { "name": { - "originalName": "Name", + "originalName": "Declaration", "camelCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "declaration", + "safeName": "declaration" }, "snakeCase": { - "unsafeName": "name", - "safeName": "name" + "unsafeName": "declaration", + "safeName": "declaration" }, "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" + "unsafeName": "DECLARATION", + "safeName": "DECLARATION" }, "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" + "unsafeName": "Declaration", + "safeName": "Declaration" } }, "fernFilepath": { @@ -43402,629 +47543,749 @@ } } }, - "typeId": "type_commons:Name" - }, - "shape": "OBJECT" - } - }, - "referencedTypes": [ - "type_commons:Name", - "type_commons:SafeAndUnsafeString" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_webhooks:WebhookPayload": { - "name": { - "name": { - "originalName": "WebhookPayload", - "camelCase": { - "unsafeName": "webhookPayload", - "safeName": "webhookPayload" - }, - "snakeCase": { - "unsafeName": "webhook_payload", - "safeName": "webhook_payload" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOK_PAYLOAD", - "safeName": "WEBHOOK_PAYLOAD" - }, - "pascalCase": { - "unsafeName": "WebhookPayload", - "safeName": "WebhookPayload" + "typeId": "type_commons:Declaration" } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + ], + "properties": [ + { + "name": { + "name": { + "originalName": "displayName", + "camelCase": { + "unsafeName": "displayName", + "safeName": "displayName" + }, + "snakeCase": { + "unsafeName": "display_name", + "safeName": "display_name" + }, + "screamingSnakeCase": { + "unsafeName": "DISPLAY_NAME", + "safeName": "DISPLAY_NAME" + }, + "pascalCase": { + "unsafeName": "DisplayName", + "safeName": "DisplayName" + } }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - }, - "typeId": "type_webhooks:WebhookPayload" - }, - "shape": { - "_type": "union", - "discriminant": { - "name": { - "originalName": "type", - "camelCase": { - "unsafeName": "type", - "safeName": "type" - }, - "snakeCase": { - "unsafeName": "type", - "safeName": "type" + "wireValue": "displayName" }, - "screamingSnakeCase": { - "unsafeName": "TYPE", - "safeName": "TYPE" + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": "STRING" + } + } }, - "pascalCase": { - "unsafeName": "Type", - "safeName": "Type" - } + "availability": null, + "docs": null }, - "wireValue": "type" - }, - "extends": [], - "baseProperties": [], - "types": [ { - "discriminantValue": { + "name": { "name": { - "originalName": "inlinedPayload", + "originalName": "origin", "camelCase": { - "unsafeName": "inlinedPayload", - "safeName": "inlinedPayload" + "unsafeName": "origin", + "safeName": "origin" }, "snakeCase": { - "unsafeName": "inlined_payload", - "safeName": "inlined_payload" + "unsafeName": "origin", + "safeName": "origin" }, "screamingSnakeCase": { - "unsafeName": "INLINED_PAYLOAD", - "safeName": "INLINED_PAYLOAD" + "unsafeName": "ORIGIN", + "safeName": "ORIGIN" }, "pascalCase": { - "unsafeName": "InlinedPayload", - "safeName": "InlinedPayload" + "unsafeName": "Origin", + "safeName": "Origin" } }, - "wireValue": "inlinedPayload" + "wireValue": "origin" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "InlinedWebhookPayload", + "originalName": "WebsocketMessageOrigin", "camelCase": { - "unsafeName": "inlinedWebhookPayload", - "safeName": "inlinedWebhookPayload" + "unsafeName": "websocketMessageOrigin", + "safeName": "websocketMessageOrigin" }, "snakeCase": { - "unsafeName": "inlined_webhook_payload", - "safeName": "inlined_webhook_payload" + "unsafeName": "websocket_message_origin", + "safeName": "websocket_message_origin" }, "screamingSnakeCase": { - "unsafeName": "INLINED_WEBHOOK_PAYLOAD", - "safeName": "INLINED_WEBHOOK_PAYLOAD" + "unsafeName": "WEBSOCKET_MESSAGE_ORIGIN", + "safeName": "WEBSOCKET_MESSAGE_ORIGIN" }, "pascalCase": { - "unsafeName": "InlinedWebhookPayload", - "safeName": "InlinedWebhookPayload" + "unsafeName": "WebsocketMessageOrigin", + "safeName": "WebsocketMessageOrigin" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:InlinedWebhookPayload" + "typeId": "type_websocket:WebsocketMessageOrigin" }, + "availability": null, "docs": null }, { - "discriminantValue": { + "name": { "name": { - "originalName": "reference", + "originalName": "body", "camelCase": { - "unsafeName": "reference", - "safeName": "reference" + "unsafeName": "body", + "safeName": "body" }, "snakeCase": { - "unsafeName": "reference", - "safeName": "reference" + "unsafeName": "body", + "safeName": "body" }, "screamingSnakeCase": { - "unsafeName": "REFERENCE", - "safeName": "REFERENCE" + "unsafeName": "BODY", + "safeName": "BODY" }, "pascalCase": { - "unsafeName": "Reference", - "safeName": "Reference" + "unsafeName": "Body", + "safeName": "Body" } }, - "wireValue": "reference" + "wireValue": "body" }, - "shape": { - "_type": "samePropertiesAsObject", + "valueType": { + "_type": "named", "name": { - "originalName": "WebhookPayloadReference", + "originalName": "WebsocketMessageBody", "camelCase": { - "unsafeName": "webhookPayloadReference", - "safeName": "webhookPayloadReference" + "unsafeName": "websocketMessageBody", + "safeName": "websocketMessageBody" }, "snakeCase": { - "unsafeName": "webhook_payload_reference", - "safeName": "webhook_payload_reference" + "unsafeName": "websocket_message_body", + "safeName": "websocket_message_body" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_PAYLOAD_REFERENCE", - "safeName": "WEBHOOK_PAYLOAD_REFERENCE" + "unsafeName": "WEBSOCKET_MESSAGE_BODY", + "safeName": "WEBSOCKET_MESSAGE_BODY" }, "pascalCase": { - "unsafeName": "WebhookPayloadReference", - "safeName": "WebhookPayloadReference" + "unsafeName": "WebsocketMessageBody", + "safeName": "WebsocketMessageBody" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:WebhookPayloadReference" + "typeId": "type_websocket:WebsocketMessageBody" }, + "availability": null, "docs": null } ] }, "referencedTypes": [ - "type_webhooks:InlinedWebhookPayload", + "type_commons:Declaration", + "type_commons:WithDocs", + "type_commons:Availability", + "type_commons:AvailabilityStatus", + "type_websocket:WebsocketMessageOrigin", + "type_websocket:WebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBody", "type_commons:Name", "type_commons:SafeAndUnsafeString", "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", - "type_webhooks:InlinedWebhookPayloadProperty", - "type_commons:WithDocs", + "type_websocket:InlinedWebsocketMessageBodyProperty", "type_commons:NameAndWireValue", "type_types:TypeReference", "type_types:ContainerType", "type_types:MapType", "type_types:Literal", "type_types:PrimitiveType", - "type_webhooks:WebhookPayloadReference" + "type_websocket:WebsocketMessageBodyReference" ], "examples": [], "availability": null, "docs": null }, - "type_webhooks:WebhookPayloadReference": { + "type_websocket:WebsocketMessageOrigin": { "name": { "name": { - "originalName": "WebhookPayloadReference", + "originalName": "WebsocketMessageOrigin", "camelCase": { - "unsafeName": "webhookPayloadReference", - "safeName": "webhookPayloadReference" + "unsafeName": "websocketMessageOrigin", + "safeName": "websocketMessageOrigin" }, "snakeCase": { - "unsafeName": "webhook_payload_reference", - "safeName": "webhook_payload_reference" + "unsafeName": "websocket_message_origin", + "safeName": "websocket_message_origin" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOK_PAYLOAD_REFERENCE", - "safeName": "WEBHOOK_PAYLOAD_REFERENCE" + "unsafeName": "WEBSOCKET_MESSAGE_ORIGIN", + "safeName": "WEBSOCKET_MESSAGE_ORIGIN" }, "pascalCase": { - "unsafeName": "WebhookPayloadReference", - "safeName": "WebhookPayloadReference" + "unsafeName": "WebsocketMessageOrigin", + "safeName": "WebsocketMessageOrigin" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:WebhookPayloadReference" + "typeId": "type_websocket:WebsocketMessageOrigin" }, "shape": { - "_type": "object", - "extends": [ + "_type": "enum", + "values": [ { "name": { - "originalName": "WithDocs", + "name": { + "originalName": "client", + "camelCase": { + "unsafeName": "client", + "safeName": "client" + }, + "snakeCase": { + "unsafeName": "client", + "safeName": "client" + }, + "screamingSnakeCase": { + "unsafeName": "CLIENT", + "safeName": "CLIENT" + }, + "pascalCase": { + "unsafeName": "Client", + "safeName": "Client" + } + }, + "wireValue": "client" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "server", + "camelCase": { + "unsafeName": "server", + "safeName": "server" + }, + "snakeCase": { + "unsafeName": "server", + "safeName": "server" + }, + "screamingSnakeCase": { + "unsafeName": "SERVER", + "safeName": "SERVER" + }, + "pascalCase": { + "unsafeName": "Server", + "safeName": "Server" + } + }, + "wireValue": "server" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [], + "examples": [], + "availability": null, + "docs": null + }, + "type_websocket:WebsocketMessageBody": { + "name": { + "name": { + "originalName": "WebsocketMessageBody", + "camelCase": { + "unsafeName": "websocketMessageBody", + "safeName": "websocketMessageBody" + }, + "snakeCase": { + "unsafeName": "websocket_message_body", + "safeName": "websocket_message_body" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE_BODY", + "safeName": "WEBSOCKET_MESSAGE_BODY" + }, + "pascalCase": { + "unsafeName": "WebsocketMessageBody", + "safeName": "WebsocketMessageBody" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", "camelCase": { - "unsafeName": "withDocs", - "safeName": "withDocs" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "with_docs", - "safeName": "with_docs" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WITH_DOCS", - "safeName": "WITH_DOCS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "WithDocs", - "safeName": "WithDocs" + "unsafeName": "Websocket", + "safeName": "Websocket" } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "typeId": "type_websocket:WebsocketMessageBody" + }, + "shape": { + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "extends": [], + "baseProperties": [], + "types": [ + { + "discriminantValue": { + "name": { + "originalName": "inlinedBody", + "camelCase": { + "unsafeName": "inlinedBody", + "safeName": "inlinedBody" + }, + "snakeCase": { + "unsafeName": "inlined_body", + "safeName": "inlined_body" + }, + "screamingSnakeCase": { + "unsafeName": "INLINED_BODY", + "safeName": "INLINED_BODY" + }, + "pascalCase": { + "unsafeName": "InlinedBody", + "safeName": "InlinedBody" + } + }, + "wireValue": "inlinedBody" + }, + "shape": { + "_type": "samePropertiesAsObject", + "name": { + "originalName": "InlinedWebsocketMessageBody", + "camelCase": { + "unsafeName": "inlinedWebsocketMessageBody", + "safeName": "inlinedWebsocketMessageBody" + }, + "snakeCase": { + "unsafeName": "inlined_websocket_message_body", + "safeName": "inlined_websocket_message_body" + }, + "screamingSnakeCase": { + "unsafeName": "INLINED_WEBSOCKET_MESSAGE_BODY", + "safeName": "INLINED_WEBSOCKET_MESSAGE_BODY" + }, + "pascalCase": { + "unsafeName": "InlinedWebsocketMessageBody", + "safeName": "InlinedWebsocketMessageBody" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", "camelCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" + "unsafeName": "Websocket", + "safeName": "Websocket" } } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } + }, + "typeId": "type_websocket:InlinedWebsocketMessageBody" }, - "typeId": "type_commons:WithDocs" - } - ], - "properties": [ + "docs": null + }, { - "name": { + "discriminantValue": { "name": { - "originalName": "payloadType", + "originalName": "reference", "camelCase": { - "unsafeName": "payloadType", - "safeName": "payloadType" + "unsafeName": "reference", + "safeName": "reference" }, "snakeCase": { - "unsafeName": "payload_type", - "safeName": "payload_type" + "unsafeName": "reference", + "safeName": "reference" }, "screamingSnakeCase": { - "unsafeName": "PAYLOAD_TYPE", - "safeName": "PAYLOAD_TYPE" + "unsafeName": "REFERENCE", + "safeName": "REFERENCE" }, "pascalCase": { - "unsafeName": "PayloadType", - "safeName": "PayloadType" + "unsafeName": "Reference", + "safeName": "Reference" } }, - "wireValue": "payloadType" + "wireValue": "reference" }, - "valueType": { - "_type": "named", + "shape": { + "_type": "samePropertiesAsObject", "name": { - "originalName": "TypeReference", + "originalName": "WebsocketMessageBodyReference", "camelCase": { - "unsafeName": "typeReference", - "safeName": "typeReference" + "unsafeName": "websocketMessageBodyReference", + "safeName": "websocketMessageBodyReference" }, "snakeCase": { - "unsafeName": "type_reference", - "safeName": "type_reference" + "unsafeName": "websocket_message_body_reference", + "safeName": "websocket_message_body_reference" }, "screamingSnakeCase": { - "unsafeName": "TYPE_REFERENCE", - "safeName": "TYPE_REFERENCE" + "unsafeName": "WEBSOCKET_MESSAGE_BODY_REFERENCE", + "safeName": "WEBSOCKET_MESSAGE_BODY_REFERENCE" }, "pascalCase": { - "unsafeName": "TypeReference", - "safeName": "TypeReference" + "unsafeName": "WebsocketMessageBodyReference", + "safeName": "WebsocketMessageBodyReference" } }, "fernFilepath": { "allParts": [ { - "originalName": "types", + "originalName": "websocket", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "types", + "originalName": "websocket", "camelCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "types", - "safeName": "types" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "TYPES", - "safeName": "TYPES" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Types", - "safeName": "Types" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_types:TypeReference" + "typeId": "type_websocket:WebsocketMessageBodyReference" }, - "availability": null, "docs": null } ] }, "referencedTypes": [ + "type_websocket:InlinedWebsocketMessageBody", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_websocket:InlinedWebsocketMessageBodyProperty", "type_commons:WithDocs", + "type_commons:NameAndWireValue", "type_types:TypeReference", "type_types:ContainerType", "type_types:MapType", "type_types:Literal", - "type_types:DeclaredTypeName", - "type_commons:TypeId", - "type_commons:FernFilepath", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", - "type_types:PrimitiveType" + "type_types:PrimitiveType", + "type_websocket:WebsocketMessageBodyReference" ], "examples": [], "availability": null, "docs": null }, - "type_webhooks:InlinedWebhookPayload": { + "type_websocket:InlinedWebsocketMessageBody": { "name": { "name": { - "originalName": "InlinedWebhookPayload", + "originalName": "InlinedWebsocketMessageBody", "camelCase": { - "unsafeName": "inlinedWebhookPayload", - "safeName": "inlinedWebhookPayload" + "unsafeName": "inlinedWebsocketMessageBody", + "safeName": "inlinedWebsocketMessageBody" }, "snakeCase": { - "unsafeName": "inlined_webhook_payload", - "safeName": "inlined_webhook_payload" + "unsafeName": "inlined_websocket_message_body", + "safeName": "inlined_websocket_message_body" }, "screamingSnakeCase": { - "unsafeName": "INLINED_WEBHOOK_PAYLOAD", - "safeName": "INLINED_WEBHOOK_PAYLOAD" + "unsafeName": "INLINED_WEBSOCKET_MESSAGE_BODY", + "safeName": "INLINED_WEBSOCKET_MESSAGE_BODY" }, "pascalCase": { - "unsafeName": "InlinedWebhookPayload", - "safeName": "InlinedWebhookPayload" + "unsafeName": "InlinedWebsocketMessageBody", + "safeName": "InlinedWebsocketMessageBody" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:InlinedWebhookPayload" + "typeId": "type_websocket:InlinedWebsocketMessageBody" }, "shape": { "_type": "object", @@ -44250,68 +48511,68 @@ "list": { "_type": "named", "name": { - "originalName": "InlinedWebhookPayloadProperty", + "originalName": "InlinedWebsocketMessageBodyProperty", "camelCase": { - "unsafeName": "inlinedWebhookPayloadProperty", - "safeName": "inlinedWebhookPayloadProperty" + "unsafeName": "inlinedWebsocketMessageBodyProperty", + "safeName": "inlinedWebsocketMessageBodyProperty" }, "snakeCase": { - "unsafeName": "inlined_webhook_payload_property", - "safeName": "inlined_webhook_payload_property" + "unsafeName": "inlined_websocket_message_body_property", + "safeName": "inlined_websocket_message_body_property" }, "screamingSnakeCase": { - "unsafeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY", - "safeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY" + "unsafeName": "INLINED_WEBSOCKET_MESSAGE_BODY_PROPERTY", + "safeName": "INLINED_WEBSOCKET_MESSAGE_BODY_PROPERTY" }, "pascalCase": { - "unsafeName": "InlinedWebhookPayloadProperty", - "safeName": "InlinedWebhookPayloadProperty" + "unsafeName": "InlinedWebsocketMessageBodyProperty", + "safeName": "InlinedWebsocketMessageBodyProperty" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:InlinedWebhookPayloadProperty" + "typeId": "type_websocket:InlinedWebsocketMessageBodyProperty" } } }, @@ -44326,7 +48587,7 @@ "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", - "type_webhooks:InlinedWebhookPayloadProperty", + "type_websocket:InlinedWebsocketMessageBodyProperty", "type_commons:WithDocs", "type_commons:NameAndWireValue", "type_types:TypeReference", @@ -44339,71 +48600,411 @@ "availability": null, "docs": null }, - "type_webhooks:InlinedWebhookPayloadProperty": { + "type_websocket:InlinedWebsocketMessageBodyProperty": { "name": { "name": { - "originalName": "InlinedWebhookPayloadProperty", + "originalName": "InlinedWebsocketMessageBodyProperty", "camelCase": { - "unsafeName": "inlinedWebhookPayloadProperty", - "safeName": "inlinedWebhookPayloadProperty" + "unsafeName": "inlinedWebsocketMessageBodyProperty", + "safeName": "inlinedWebsocketMessageBodyProperty" }, "snakeCase": { - "unsafeName": "inlined_webhook_payload_property", - "safeName": "inlined_webhook_payload_property" + "unsafeName": "inlined_websocket_message_body_property", + "safeName": "inlined_websocket_message_body_property" }, "screamingSnakeCase": { - "unsafeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY", - "safeName": "INLINED_WEBHOOK_PAYLOAD_PROPERTY" + "unsafeName": "INLINED_WEBSOCKET_MESSAGE_BODY_PROPERTY", + "safeName": "INLINED_WEBSOCKET_MESSAGE_BODY_PROPERTY" }, "pascalCase": { - "unsafeName": "InlinedWebhookPayloadProperty", - "safeName": "InlinedWebhookPayloadProperty" + "unsafeName": "InlinedWebsocketMessageBodyProperty", + "safeName": "InlinedWebsocketMessageBodyProperty" } }, "fernFilepath": { "allParts": [ { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } ], "packagePath": [], "file": { - "originalName": "webhooks", + "originalName": "websocket", "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" + "unsafeName": "websocket", + "safeName": "websocket" }, "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" }, "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" + "unsafeName": "Websocket", + "safeName": "Websocket" } } }, - "typeId": "type_webhooks:InlinedWebhookPayloadProperty" + "typeId": "type_websocket:InlinedWebsocketMessageBodyProperty" + }, + "shape": { + "_type": "object", + "extends": [ + { + "name": { + "originalName": "WithDocs", + "camelCase": { + "unsafeName": "withDocs", + "safeName": "withDocs" + }, + "snakeCase": { + "unsafeName": "with_docs", + "safeName": "with_docs" + }, + "screamingSnakeCase": { + "unsafeName": "WITH_DOCS", + "safeName": "WITH_DOCS" + }, + "pascalCase": { + "unsafeName": "WithDocs", + "safeName": "WithDocs" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:WithDocs" + } + ], + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "NameAndWireValue", + "camelCase": { + "unsafeName": "nameAndWireValue", + "safeName": "nameAndWireValue" + }, + "snakeCase": { + "unsafeName": "name_and_wire_value", + "safeName": "name_and_wire_value" + }, + "screamingSnakeCase": { + "unsafeName": "NAME_AND_WIRE_VALUE", + "safeName": "NAME_AND_WIRE_VALUE" + }, + "pascalCase": { + "unsafeName": "NameAndWireValue", + "safeName": "NameAndWireValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + ], + "packagePath": [], + "file": { + "originalName": "commons", + "camelCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "snakeCase": { + "unsafeName": "commons", + "safeName": "commons" + }, + "screamingSnakeCase": { + "unsafeName": "COMMONS", + "safeName": "COMMONS" + }, + "pascalCase": { + "unsafeName": "Commons", + "safeName": "Commons" + } + } + }, + "typeId": "type_commons:NameAndWireValue" + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "valueType", + "camelCase": { + "unsafeName": "valueType", + "safeName": "valueType" + }, + "snakeCase": { + "unsafeName": "value_type", + "safeName": "value_type" + }, + "screamingSnakeCase": { + "unsafeName": "VALUE_TYPE", + "safeName": "VALUE_TYPE" + }, + "pascalCase": { + "unsafeName": "ValueType", + "safeName": "ValueType" + } + }, + "wireValue": "valueType" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "TypeReference", + "camelCase": { + "unsafeName": "typeReference", + "safeName": "typeReference" + }, + "snakeCase": { + "unsafeName": "type_reference", + "safeName": "type_reference" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE_REFERENCE", + "safeName": "TYPE_REFERENCE" + }, + "pascalCase": { + "unsafeName": "TypeReference", + "safeName": "TypeReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + ], + "packagePath": [], + "file": { + "originalName": "types", + "camelCase": { + "unsafeName": "types", + "safeName": "types" + }, + "snakeCase": { + "unsafeName": "types", + "safeName": "types" + }, + "screamingSnakeCase": { + "unsafeName": "TYPES", + "safeName": "TYPES" + }, + "pascalCase": { + "unsafeName": "Types", + "safeName": "Types" + } + } + }, + "typeId": "type_types:TypeReference" + }, + "availability": null, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_commons:WithDocs", + "type_commons:NameAndWireValue", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", + "type_types:TypeReference", + "type_types:ContainerType", + "type_types:MapType", + "type_types:Literal", + "type_types:DeclaredTypeName", + "type_commons:TypeId", + "type_commons:FernFilepath", + "type_types:PrimitiveType" + ], + "examples": [], + "availability": null, + "docs": null + }, + "type_websocket:WebsocketMessageBodyReference": { + "name": { + "name": { + "originalName": "WebsocketMessageBodyReference", + "camelCase": { + "unsafeName": "websocketMessageBodyReference", + "safeName": "websocketMessageBodyReference" + }, + "snakeCase": { + "unsafeName": "websocket_message_body_reference", + "safeName": "websocket_message_body_reference" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET_MESSAGE_BODY_REFERENCE", + "safeName": "WEBSOCKET_MESSAGE_BODY_REFERENCE" + }, + "pascalCase": { + "unsafeName": "WebsocketMessageBodyReference", + "safeName": "WebsocketMessageBodyReference" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "typeId": "type_websocket:WebsocketMessageBodyReference" }, "shape": { "_type": "object", @@ -44478,117 +49079,25 @@ { "name": { "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - }, - "wireValue": "name" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "NameAndWireValue", - "camelCase": { - "unsafeName": "nameAndWireValue", - "safeName": "nameAndWireValue" - }, - "snakeCase": { - "unsafeName": "name_and_wire_value", - "safeName": "name_and_wire_value" - }, - "screamingSnakeCase": { - "unsafeName": "NAME_AND_WIRE_VALUE", - "safeName": "NAME_AND_WIRE_VALUE" - }, - "pascalCase": { - "unsafeName": "NameAndWireValue", - "safeName": "NameAndWireValue" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - ], - "packagePath": [], - "file": { - "originalName": "commons", - "camelCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "snakeCase": { - "unsafeName": "commons", - "safeName": "commons" - }, - "screamingSnakeCase": { - "unsafeName": "COMMONS", - "safeName": "COMMONS" - }, - "pascalCase": { - "unsafeName": "Commons", - "safeName": "Commons" - } - } - }, - "typeId": "type_commons:NameAndWireValue" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "valueType", + "originalName": "bodyType", "camelCase": { - "unsafeName": "valueType", - "safeName": "valueType" + "unsafeName": "bodyType", + "safeName": "bodyType" }, "snakeCase": { - "unsafeName": "value_type", - "safeName": "value_type" + "unsafeName": "body_type", + "safeName": "body_type" }, "screamingSnakeCase": { - "unsafeName": "VALUE_TYPE", - "safeName": "VALUE_TYPE" + "unsafeName": "BODY_TYPE", + "safeName": "BODY_TYPE" }, "pascalCase": { - "unsafeName": "ValueType", - "safeName": "ValueType" + "unsafeName": "BodyType", + "safeName": "BodyType" } }, - "wireValue": "valueType" + "wireValue": "bodyType" }, "valueType": { "_type": "named", @@ -44663,9 +49172,6 @@ }, "referencedTypes": [ "type_commons:WithDocs", - "type_commons:NameAndWireValue", - "type_commons:Name", - "type_commons:SafeAndUnsafeString", "type_types:TypeReference", "type_types:ContainerType", "type_types:MapType", @@ -44673,139 +49179,13 @@ "type_types:DeclaredTypeName", "type_commons:TypeId", "type_commons:FernFilepath", + "type_commons:Name", + "type_commons:SafeAndUnsafeString", "type_types:PrimitiveType" ], "examples": [], "availability": null, "docs": null - }, - "type_webhooks:WebhookHttpMethod": { - "name": { - "name": { - "originalName": "WebhookHttpMethod", - "camelCase": { - "unsafeName": "webhookHttpMethod", - "safeName": "webhookHttpMethod" - }, - "snakeCase": { - "unsafeName": "webhook_http_method", - "safeName": "webhook_http_method" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOK_HTTP_METHOD", - "safeName": "WEBHOOK_HTTP_METHOD" - }, - "pascalCase": { - "unsafeName": "WebhookHttpMethod", - "safeName": "WebhookHttpMethod" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - ], - "packagePath": [], - "file": { - "originalName": "webhooks", - "camelCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "snakeCase": { - "unsafeName": "webhooks", - "safeName": "webhooks" - }, - "screamingSnakeCase": { - "unsafeName": "WEBHOOKS", - "safeName": "WEBHOOKS" - }, - "pascalCase": { - "unsafeName": "Webhooks", - "safeName": "Webhooks" - } - } - }, - "typeId": "type_webhooks:WebhookHttpMethod" - }, - "shape": { - "_type": "enum", - "values": [ - { - "name": { - "name": { - "originalName": "GET", - "camelCase": { - "unsafeName": "get", - "safeName": "get" - }, - "snakeCase": { - "unsafeName": "get", - "safeName": "get" - }, - "screamingSnakeCase": { - "unsafeName": "GET", - "safeName": "GET" - }, - "pascalCase": { - "unsafeName": "Get", - "safeName": "Get" - } - }, - "wireValue": "GET" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "POST", - "camelCase": { - "unsafeName": "post", - "safeName": "post" - }, - "snakeCase": { - "unsafeName": "post", - "safeName": "post" - }, - "screamingSnakeCase": { - "unsafeName": "POST", - "safeName": "POST" - }, - "pascalCase": { - "unsafeName": "Post", - "safeName": "Post" - } - }, - "wireValue": "POST" - }, - "availability": null, - "docs": null - } - ] - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null } }, "errors": {}, @@ -44864,6 +49244,7 @@ "type_commons:TypeId", "type_commons:ErrorId", "type_commons:WebhookGroupId", + "type_commons:WebsocketChannelId", "type_commons:Declaration", "type_commons:Availability", "type_commons:AvailabilityStatus", @@ -44915,6 +49296,10 @@ "type_http:ResponseErrors", "type_http:ResponseError", "type_http:ExampleEndpointCall", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage", "type_http:ExamplePathParameter", "type_http:ExampleQueryParameter", "type_http:ExampleHeader", @@ -44981,7 +49366,15 @@ "type_webhooks:WebhookPayloadReference", "type_webhooks:InlinedWebhookPayload", "type_webhooks:InlinedWebhookPayloadProperty", - "type_webhooks:WebhookHttpMethod" + "type_webhooks:WebhookHttpMethod", + "type_websocket:WebsocketMessageId", + "type_websocket:WebsocketChannel", + "type_websocket:WebsocketMessage", + "type_websocket:WebsocketMessageOrigin", + "type_websocket:WebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBodyProperty", + "type_websocket:WebsocketMessageBodyReference" ] }, "webhookGroups": {}, @@ -45144,6 +49537,7 @@ "type_commons:TypeId", "type_commons:ErrorId", "type_commons:WebhookGroupId", + "type_commons:WebsocketChannelId", "type_commons:Declaration", "type_commons:Availability", "type_commons:AvailabilityStatus" @@ -45487,6 +49881,10 @@ "type_http:ResponseErrors", "type_http:ResponseError", "type_http:ExampleEndpointCall", + "type_http:ExampleCodeSample", + "type_http:ExampleCodeSampleLanguage", + "type_http:ExampleCodeSampleSdk", + "type_http:SupportedSdkLanguage", "type_http:ExamplePathParameter", "type_http:ExampleQueryParameter", "type_http:ExampleHeader", @@ -45853,6 +50251,87 @@ "webhooks": null, "hasEndpointsInTree": false, "docs": null + }, + "subpackage_websocket": { + "name": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + ], + "packagePath": [], + "file": { + "originalName": "websocket", + "camelCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "snakeCase": { + "unsafeName": "websocket", + "safeName": "websocket" + }, + "screamingSnakeCase": { + "unsafeName": "WEBSOCKET", + "safeName": "WEBSOCKET" + }, + "pascalCase": { + "unsafeName": "Websocket", + "safeName": "Websocket" + } + } + }, + "service": null, + "types": [ + "type_websocket:WebsocketMessageId", + "type_websocket:WebsocketChannel", + "type_websocket:WebsocketMessage", + "type_websocket:WebsocketMessageOrigin", + "type_websocket:WebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBody", + "type_websocket:InlinedWebsocketMessageBodyProperty", + "type_websocket:WebsocketMessageBodyReference" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "hasEndpointsInTree": false, + "docs": null } }, "rootPackage": { @@ -45874,7 +50353,8 @@ "subpackage_ir", "subpackage_types", "subpackage_variables", - "subpackage_webhooks" + "subpackage_webhooks", + "subpackage_websocket" ], "webhooks": null, "navigationConfig": null, diff --git a/generators/go/seed/sdk/bytes/core/time.go b/generators/go/internal/fern/ir/core/time.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/time.go rename to generators/go/internal/fern/ir/core/time.go diff --git a/generators/go/internal/fern/ir/types.go b/generators/go/internal/fern/ir/types.go index 3407d0702a2..b88427e2311 100644 --- a/generators/go/internal/fern/ir/types.go +++ b/generators/go/internal/fern/ir/types.go @@ -7,14 +7,14 @@ import ( fmt "fmt" time "time" - core "github.com/fern-api/fern-go/internal/generator/model/core" + core "github.com/fern-api/fern-go/internal/fern/ir/core" uuid "github.com/google/uuid" ) type ApiAuth struct { - Docs *string `json:"docs,omitempty"` - Requirement AuthSchemesRequirement `json:"requirement,omitempty"` - Schemes []*AuthScheme `json:"schemes,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Requirement AuthSchemesRequirement `json:"requirement,omitempty" url:"requirement,omitempty"` + Schemes []*AuthScheme `json:"schemes,omitempty" url:"schemes,omitempty"` } func (a *ApiAuth) String() string { @@ -150,13 +150,13 @@ func (a AuthSchemesRequirement) Ptr() *AuthSchemesRequirement { } type BasicAuthScheme struct { - Docs *string `json:"docs,omitempty"` - Username *Name `json:"username,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Username *Name `json:"username,omitempty" url:"username,omitempty"` // The environment variable the SDK should use to read the username. - UsernameEnvVar *EnvironmentVariable `json:"usernameEnvVar,omitempty"` - Password *Name `json:"password,omitempty"` + UsernameEnvVar *EnvironmentVariable `json:"usernameEnvVar,omitempty" url:"usernameEnvVar,omitempty"` + Password *Name `json:"password,omitempty" url:"password,omitempty"` // The environment variable the SDK should use to read the password. - PasswordEnvVar *EnvironmentVariable `json:"passwordEnvVar,omitempty"` + PasswordEnvVar *EnvironmentVariable `json:"passwordEnvVar,omitempty" url:"passwordEnvVar,omitempty"` } func (b *BasicAuthScheme) String() string { @@ -167,10 +167,10 @@ func (b *BasicAuthScheme) String() string { } type BearerAuthScheme struct { - Docs *string `json:"docs,omitempty"` - Token *Name `json:"token,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Token *Name `json:"token,omitempty" url:"token,omitempty"` // The environment variable the SDK should use to read the token. - TokenEnvVar *EnvironmentVariable `json:"tokenEnvVar,omitempty"` + TokenEnvVar *EnvironmentVariable `json:"tokenEnvVar,omitempty" url:"tokenEnvVar,omitempty"` } func (b *BearerAuthScheme) String() string { @@ -183,12 +183,12 @@ func (b *BearerAuthScheme) String() string { type EnvironmentVariable = string type HeaderAuthScheme struct { - Docs *string `json:"docs,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` - Prefix *string `json:"prefix,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` + Prefix *string `json:"prefix,omitempty" url:"prefix,omitempty"` // The environment variable the SDK should use to read the header. - HeaderEnvVar *EnvironmentVariable `json:"headerEnvVar,omitempty"` + HeaderEnvVar *EnvironmentVariable `json:"headerEnvVar,omitempty" url:"headerEnvVar,omitempty"` } func (h *HeaderAuthScheme) String() string { @@ -199,8 +199,8 @@ func (h *HeaderAuthScheme) String() string { } type Availability struct { - Status AvailabilityStatus `json:"status,omitempty"` - Message *string `json:"message,omitempty"` + Status AvailabilityStatus `json:"status,omitempty" url:"status,omitempty"` + Message *string `json:"message,omitempty" url:"message,omitempty"` } func (a *Availability) String() string { @@ -239,8 +239,8 @@ func (a AvailabilityStatus) Ptr() *AvailabilityStatus { } type Declaration struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` } func (d *Declaration) String() string { @@ -260,7 +260,7 @@ type ErrorId = string // For example, in Python we escape strings that contain single or double quotes by using triple quotes, // in Go we use backticks, etc. type EscapedString struct { - Original string `json:"original"` + Original string `json:"original" url:"original"` } func (e *EscapedString) String() string { @@ -271,9 +271,9 @@ func (e *EscapedString) String() string { } type FernFilepath struct { - AllParts []*Name `json:"allParts,omitempty"` - PackagePath []*Name `json:"packagePath,omitempty"` - File *Name `json:"file,omitempty"` + AllParts []*Name `json:"allParts,omitempty" url:"allParts,omitempty"` + PackagePath []*Name `json:"packagePath,omitempty" url:"packagePath,omitempty"` + File *Name `json:"file,omitempty" url:"file,omitempty"` } func (f *FernFilepath) String() string { @@ -284,11 +284,11 @@ func (f *FernFilepath) String() string { } type Name struct { - OriginalName string `json:"originalName"` - CamelCase *SafeAndUnsafeString `json:"camelCase,omitempty"` - PascalCase *SafeAndUnsafeString `json:"pascalCase,omitempty"` - SnakeCase *SafeAndUnsafeString `json:"snakeCase,omitempty"` - ScreamingSnakeCase *SafeAndUnsafeString `json:"screamingSnakeCase,omitempty"` + OriginalName string `json:"originalName" url:"originalName"` + CamelCase *SafeAndUnsafeString `json:"camelCase,omitempty" url:"camelCase,omitempty"` + PascalCase *SafeAndUnsafeString `json:"pascalCase,omitempty" url:"pascalCase,omitempty"` + SnakeCase *SafeAndUnsafeString `json:"snakeCase,omitempty" url:"snakeCase,omitempty"` + ScreamingSnakeCase *SafeAndUnsafeString `json:"screamingSnakeCase,omitempty" url:"screamingSnakeCase,omitempty"` } func (n *Name) String() string { @@ -299,8 +299,8 @@ func (n *Name) String() string { } type NameAndWireValue struct { - WireValue string `json:"wireValue"` - Name *Name `json:"name,omitempty"` + WireValue string `json:"wireValue" url:"wireValue"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` } func (n *NameAndWireValue) String() string { @@ -312,9 +312,9 @@ func (n *NameAndWireValue) String() string { type SafeAndUnsafeString struct { // this name might overlap with reserved keywords of the language being generated - UnsafeName string `json:"unsafeName"` + UnsafeName string `json:"unsafeName" url:"unsafeName"` // this name will NOT overlap with reserved keywords of the language being generated - SafeName string `json:"safeName"` + SafeName string `json:"safeName" url:"safeName"` } func (s *SafeAndUnsafeString) String() string { @@ -332,8 +332,10 @@ type TypeId = string type WebhookGroupId = string +type WebsocketChannelId = string + type WithDocs struct { - Docs *string `json:"docs,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` } func (w *WithDocs) String() string { @@ -344,7 +346,7 @@ func (w *WithDocs) String() string { } type WithJsonExample struct { - JsonExample interface{} `json:"jsonExample,omitempty"` + JsonExample interface{} `json:"jsonExample,omitempty" url:"jsonExample,omitempty"` } func (w *WithJsonExample) String() string { @@ -355,7 +357,7 @@ func (w *WithJsonExample) String() string { } type Constants struct { - ErrorInstanceIdKey *NameAndWireValue `json:"errorInstanceIdKey,omitempty"` + ErrorInstanceIdKey *NameAndWireValue `json:"errorInstanceIdKey,omitempty" url:"errorInstanceIdKey,omitempty"` } func (c *Constants) String() string { @@ -368,8 +370,8 @@ func (c *Constants) String() string { type EnvironmentBaseUrlId = string type EnvironmentBaseUrlWithId struct { - Id EnvironmentBaseUrlId `json:"id"` - Name *Name `json:"name,omitempty"` + Id EnvironmentBaseUrlId `json:"id" url:"id"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` } func (e *EnvironmentBaseUrlWithId) String() string { @@ -464,8 +466,8 @@ func (e *Environments) Accept(visitor EnvironmentsVisitor) error { } type EnvironmentsConfig struct { - DefaultEnvironment *EnvironmentId `json:"defaultEnvironment,omitempty"` - Environments *Environments `json:"environments,omitempty"` + DefaultEnvironment *EnvironmentId `json:"defaultEnvironment,omitempty" url:"defaultEnvironment,omitempty"` + Environments *Environments `json:"environments,omitempty" url:"environments,omitempty"` } func (e *EnvironmentsConfig) String() string { @@ -476,10 +478,10 @@ func (e *EnvironmentsConfig) String() string { } type MultipleBaseUrlsEnvironment struct { - Docs *string `json:"docs,omitempty"` - Id EnvironmentId `json:"id"` - Name *Name `json:"name,omitempty"` - Urls map[EnvironmentBaseUrlId]EnvironmentUrl `json:"urls,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Id EnvironmentId `json:"id" url:"id"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Urls map[EnvironmentBaseUrlId]EnvironmentUrl `json:"urls,omitempty" url:"urls,omitempty"` } func (m *MultipleBaseUrlsEnvironment) String() string { @@ -490,8 +492,8 @@ func (m *MultipleBaseUrlsEnvironment) String() string { } type MultipleBaseUrlsEnvironments struct { - BaseUrls []*EnvironmentBaseUrlWithId `json:"baseUrls,omitempty"` - Environments []*MultipleBaseUrlsEnvironment `json:"environments,omitempty"` + BaseUrls []*EnvironmentBaseUrlWithId `json:"baseUrls,omitempty" url:"baseUrls,omitempty"` + Environments []*MultipleBaseUrlsEnvironment `json:"environments,omitempty" url:"environments,omitempty"` } func (m *MultipleBaseUrlsEnvironments) String() string { @@ -502,10 +504,10 @@ func (m *MultipleBaseUrlsEnvironments) String() string { } type SingleBaseUrlEnvironment struct { - Docs *string `json:"docs,omitempty"` - Id EnvironmentId `json:"id"` - Name *Name `json:"name,omitempty"` - Url EnvironmentUrl `json:"url"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Id EnvironmentId `json:"id" url:"id"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Url EnvironmentUrl `json:"url" url:"url"` } func (s *SingleBaseUrlEnvironment) String() string { @@ -516,7 +518,7 @@ func (s *SingleBaseUrlEnvironment) String() string { } type SingleBaseUrlEnvironments struct { - Environments []*SingleBaseUrlEnvironment `json:"environments,omitempty"` + Environments []*SingleBaseUrlEnvironment `json:"environments,omitempty" url:"environments,omitempty"` } func (s *SingleBaseUrlEnvironments) String() string { @@ -527,9 +529,9 @@ func (s *SingleBaseUrlEnvironments) String() string { } type DeclaredErrorName struct { - ErrorId ErrorId `json:"errorId"` - FernFilepath *FernFilepath `json:"fernFilepath,omitempty"` - Name *Name `json:"name,omitempty"` + ErrorId ErrorId `json:"errorId" url:"errorId"` + FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` } func (d *DeclaredErrorName) String() string { @@ -540,11 +542,11 @@ func (d *DeclaredErrorName) String() string { } type ErrorDeclaration struct { - Docs *string `json:"docs,omitempty"` - Name *DeclaredErrorName `json:"name,omitempty"` - DiscriminantValue *NameAndWireValue `json:"discriminantValue,omitempty"` - Type *TypeReference `json:"type,omitempty"` - StatusCode int `json:"statusCode"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *DeclaredErrorName `json:"name,omitempty" url:"name,omitempty"` + DiscriminantValue *NameAndWireValue `json:"discriminantValue,omitempty" url:"discriminantValue,omitempty"` + Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` + StatusCode int `json:"statusCode" url:"statusCode"` } func (e *ErrorDeclaration) String() string { @@ -635,8 +637,8 @@ func (e *ErrorDeclarationDiscriminantValue) Accept(visitor ErrorDeclarationDiscr } type BytesRequest struct { - IsOptional bool `json:"isOptional"` - ContentType *string `json:"contentType,omitempty"` + IsOptional bool `json:"isOptional" url:"isOptional"` + ContentType *string `json:"contentType,omitempty" url:"contentType,omitempty"` } func (b *BytesRequest) String() string { @@ -647,7 +649,7 @@ func (b *BytesRequest) String() string { } type DeclaredServiceName struct { - FernFilepath *FernFilepath `json:"fernFilepath,omitempty"` + FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` } func (d *DeclaredServiceName) String() string { @@ -659,18 +661,137 @@ func (d *DeclaredServiceName) String() string { type EndpointName = *Name +type ExampleCodeSample struct { + Type string + Language *ExampleCodeSampleLanguage + Sdk *ExampleCodeSampleSdk +} + +func NewExampleCodeSampleFromLanguage(value *ExampleCodeSampleLanguage) *ExampleCodeSample { + return &ExampleCodeSample{Type: "language", Language: value} +} + +func NewExampleCodeSampleFromSdk(value *ExampleCodeSampleSdk) *ExampleCodeSample { + return &ExampleCodeSample{Type: "sdk", Sdk: value} +} + +func (e *ExampleCodeSample) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "language": + value := new(ExampleCodeSampleLanguage) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Language = value + case "sdk": + value := new(ExampleCodeSampleSdk) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Sdk = value + } + return nil +} + +func (e ExampleCodeSample) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "language": + var marshaler = struct { + Type string `json:"type"` + *ExampleCodeSampleLanguage + }{ + Type: e.Type, + ExampleCodeSampleLanguage: e.Language, + } + return json.Marshal(marshaler) + case "sdk": + var marshaler = struct { + Type string `json:"type"` + *ExampleCodeSampleSdk + }{ + Type: e.Type, + ExampleCodeSampleSdk: e.Sdk, + } + return json.Marshal(marshaler) + } +} + +type ExampleCodeSampleVisitor interface { + VisitLanguage(*ExampleCodeSampleLanguage) error + VisitSdk(*ExampleCodeSampleSdk) error +} + +func (e *ExampleCodeSample) Accept(visitor ExampleCodeSampleVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "language": + return visitor.VisitLanguage(e.Language) + case "sdk": + return visitor.VisitSdk(e.Sdk) + } +} + +// This is intended to co-exist with the auto-generated code samples. +type ExampleCodeSampleLanguage struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + // Override the example name. + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Language string `json:"language" url:"language"` + Code string `json:"code" url:"code"` + // The command to install the dependencies for the code sample. + // For example, `npm install` or `pip install -r requirements.txt`. + Install *string `json:"install,omitempty" url:"install,omitempty"` +} + +func (e *ExampleCodeSampleLanguage) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +// This will be used to replace the auto-generated code samples. +type ExampleCodeSampleSdk struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + // Override the example name. + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Sdk SupportedSdkLanguage `json:"sdk,omitempty" url:"sdk,omitempty"` + Code string `json:"code" url:"code"` +} + +func (e *ExampleCodeSampleSdk) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + type ExampleEndpointCall struct { - Docs *string `json:"docs,omitempty"` - Name *Name `json:"name,omitempty"` - Url string `json:"url"` - RootPathParameters []*ExamplePathParameter `json:"rootPathParameters,omitempty"` - ServicePathParameters []*ExamplePathParameter `json:"servicePathParameters,omitempty"` - EndpointPathParameters []*ExamplePathParameter `json:"endpointPathParameters,omitempty"` - ServiceHeaders []*ExampleHeader `json:"serviceHeaders,omitempty"` - EndpointHeaders []*ExampleHeader `json:"endpointHeaders,omitempty"` - QueryParameters []*ExampleQueryParameter `json:"queryParameters,omitempty"` - Request *ExampleRequestBody `json:"request,omitempty"` - Response *ExampleResponse `json:"response,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Url string `json:"url" url:"url"` + RootPathParameters []*ExamplePathParameter `json:"rootPathParameters,omitempty" url:"rootPathParameters,omitempty"` + ServicePathParameters []*ExamplePathParameter `json:"servicePathParameters,omitempty" url:"servicePathParameters,omitempty"` + EndpointPathParameters []*ExamplePathParameter `json:"endpointPathParameters,omitempty" url:"endpointPathParameters,omitempty"` + ServiceHeaders []*ExampleHeader `json:"serviceHeaders,omitempty" url:"serviceHeaders,omitempty"` + EndpointHeaders []*ExampleHeader `json:"endpointHeaders,omitempty" url:"endpointHeaders,omitempty"` + QueryParameters []*ExampleQueryParameter `json:"queryParameters,omitempty" url:"queryParameters,omitempty"` + Request *ExampleRequestBody `json:"request,omitempty" url:"request,omitempty"` + Response *ExampleResponse `json:"response,omitempty" url:"response,omitempty"` + // Hand-written code samples for this endpoint. These code samples should match the + // example that it's attached to, so that we can spin up an API Playground with + // the code sample that's being displayed in the API Reference. + CodeSamples []*ExampleCodeSample `json:"codeSamples,omitempty" url:"codeSamples,omitempty"` } func (e *ExampleEndpointCall) String() string { @@ -681,8 +802,8 @@ func (e *ExampleEndpointCall) String() string { } type ExampleEndpointErrorResponse struct { - Error *DeclaredErrorName `json:"error,omitempty"` - Body *ExampleTypeReference `json:"body,omitempty"` + Error *DeclaredErrorName `json:"error,omitempty" url:"error,omitempty"` + Body *ExampleTypeReference `json:"body,omitempty" url:"body,omitempty"` } func (e *ExampleEndpointErrorResponse) String() string { @@ -693,7 +814,7 @@ func (e *ExampleEndpointErrorResponse) String() string { } type ExampleEndpointSuccessResponse struct { - Body *ExampleTypeReference `json:"body,omitempty"` + Body *ExampleTypeReference `json:"body,omitempty" url:"body,omitempty"` } func (e *ExampleEndpointSuccessResponse) String() string { @@ -704,8 +825,8 @@ func (e *ExampleEndpointSuccessResponse) String() string { } type ExampleHeader struct { - Name *NameAndWireValue `json:"name,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` } func (e *ExampleHeader) String() string { @@ -716,8 +837,8 @@ func (e *ExampleHeader) String() string { } type ExampleInlinedRequestBody struct { - JsonExample interface{} `json:"jsonExample,omitempty"` - Properties []*ExampleInlinedRequestBodyProperty `json:"properties,omitempty"` + JsonExample interface{} `json:"jsonExample,omitempty" url:"jsonExample,omitempty"` + Properties []*ExampleInlinedRequestBodyProperty `json:"properties,omitempty" url:"properties,omitempty"` } func (e *ExampleInlinedRequestBody) String() string { @@ -728,11 +849,11 @@ func (e *ExampleInlinedRequestBody) String() string { } type ExampleInlinedRequestBodyProperty struct { - Name *NameAndWireValue `json:"name,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` // This property may have been brought in via extension. originalTypeDeclaration // is the name of the type that contains this property - OriginalTypeDeclaration *DeclaredTypeName `json:"originalTypeDeclaration,omitempty"` + OriginalTypeDeclaration *DeclaredTypeName `json:"originalTypeDeclaration,omitempty" url:"originalTypeDeclaration,omitempty"` } func (e *ExampleInlinedRequestBodyProperty) String() string { @@ -743,8 +864,8 @@ func (e *ExampleInlinedRequestBodyProperty) String() string { } type ExamplePathParameter struct { - Name *Name `json:"name,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` } func (e *ExamplePathParameter) String() string { @@ -755,8 +876,8 @@ func (e *ExamplePathParameter) String() string { } type ExampleQueryParameter struct { - Name *NameAndWireValue `json:"name,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` } func (e *ExampleQueryParameter) String() string { @@ -927,7 +1048,7 @@ func (e *ExampleResponse) Accept(visitor ExampleResponseVisitor) error { } type FileDownloadResponse struct { - Docs *string `json:"docs,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` } func (f *FileDownloadResponse) String() string { @@ -938,8 +1059,8 @@ func (f *FileDownloadResponse) String() string { } type FileProperty struct { - Key *NameAndWireValue `json:"key,omitempty"` - IsOptional bool `json:"isOptional"` + Key *NameAndWireValue `json:"key,omitempty" url:"key,omitempty"` + IsOptional bool `json:"isOptional" url:"isOptional"` } func (f *FileProperty) String() string { @@ -950,8 +1071,8 @@ func (f *FileProperty) String() string { } type FileUploadRequest struct { - Name *Name `json:"name,omitempty"` - Properties []*FileUploadRequestProperty `json:"properties,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Properties []*FileUploadRequestProperty `json:"properties,omitempty" url:"properties,omitempty"` } func (f *FileUploadRequest) String() string { @@ -1042,26 +1163,26 @@ func (f *FileUploadRequestProperty) Accept(visitor FileUploadRequestPropertyVisi } type HttpEndpoint struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Id EndpointId `json:"id"` - Name EndpointName `json:"name,omitempty"` - DisplayName *string `json:"displayName,omitempty"` - Method HttpMethod `json:"method,omitempty"` - Headers []*HttpHeader `json:"headers,omitempty"` - BaseUrl *EnvironmentBaseUrlId `json:"baseUrl,omitempty"` - Path *HttpPath `json:"path,omitempty"` - FullPath *HttpPath `json:"fullPath,omitempty"` - PathParameters []*PathParameter `json:"pathParameters,omitempty"` - AllPathParameters []*PathParameter `json:"allPathParameters,omitempty"` - QueryParameters []*QueryParameter `json:"queryParameters,omitempty"` - RequestBody *HttpRequestBody `json:"requestBody,omitempty"` - SdkRequest *SdkRequest `json:"sdkRequest,omitempty"` - Response *HttpResponse `json:"response,omitempty"` - Errors ResponseErrors `json:"errors,omitempty"` - Auth bool `json:"auth"` - Idempotent bool `json:"idempotent"` - Examples []*ExampleEndpointCall `json:"examples,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Id EndpointId `json:"id" url:"id"` + Name EndpointName `json:"name,omitempty" url:"name,omitempty"` + DisplayName *string `json:"displayName,omitempty" url:"displayName,omitempty"` + Method HttpMethod `json:"method,omitempty" url:"method,omitempty"` + Headers []*HttpHeader `json:"headers,omitempty" url:"headers,omitempty"` + BaseUrl *EnvironmentBaseUrlId `json:"baseUrl,omitempty" url:"baseUrl,omitempty"` + Path *HttpPath `json:"path,omitempty" url:"path,omitempty"` + FullPath *HttpPath `json:"fullPath,omitempty" url:"fullPath,omitempty"` + PathParameters []*PathParameter `json:"pathParameters,omitempty" url:"pathParameters,omitempty"` + AllPathParameters []*PathParameter `json:"allPathParameters,omitempty" url:"allPathParameters,omitempty"` + QueryParameters []*QueryParameter `json:"queryParameters,omitempty" url:"queryParameters,omitempty"` + RequestBody *HttpRequestBody `json:"requestBody,omitempty" url:"requestBody,omitempty"` + SdkRequest *SdkRequest `json:"sdkRequest,omitempty" url:"sdkRequest,omitempty"` + Response *HttpResponse `json:"response,omitempty" url:"response,omitempty"` + Errors ResponseErrors `json:"errors,omitempty" url:"errors,omitempty"` + Auth bool `json:"auth" url:"auth"` + Idempotent bool `json:"idempotent" url:"idempotent"` + Examples []*ExampleEndpointCall `json:"examples,omitempty" url:"examples,omitempty"` } func (h *HttpEndpoint) String() string { @@ -1072,10 +1193,10 @@ func (h *HttpEndpoint) String() string { } type HttpHeader struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` } func (h *HttpHeader) String() string { @@ -1117,8 +1238,8 @@ func (h HttpMethod) Ptr() *HttpMethod { } type HttpPath struct { - Head string `json:"head"` - Parts []*HttpPathPart `json:"parts,omitempty"` + Head string `json:"head" url:"head"` + Parts []*HttpPathPart `json:"parts,omitempty" url:"parts,omitempty"` } func (h *HttpPath) String() string { @@ -1129,8 +1250,8 @@ func (h *HttpPath) String() string { } type HttpPathPart struct { - PathParameter string `json:"pathParameter"` - Tail string `json:"tail"` + PathParameter string `json:"pathParameter" url:"pathParameter"` + Tail string `json:"tail" url:"tail"` } func (h *HttpPathPart) String() string { @@ -1267,9 +1388,9 @@ func (h *HttpRequestBody) Accept(visitor HttpRequestBodyVisitor) error { } type HttpRequestBodyReference struct { - Docs *string `json:"docs,omitempty"` - RequestBodyType *TypeReference `json:"requestBodyType,omitempty"` - ContentType *string `json:"contentType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + RequestBodyType *TypeReference `json:"requestBodyType,omitempty" url:"requestBodyType,omitempty"` + ContentType *string `json:"contentType,omitempty" url:"contentType,omitempty"` } func (h *HttpRequestBodyReference) String() string { @@ -1408,13 +1529,13 @@ func (h *HttpResponse) Accept(visitor HttpResponseVisitor) error { } type HttpService struct { - Availability *Availability `json:"availability,omitempty"` - Name *DeclaredServiceName `json:"name,omitempty"` - DisplayName *string `json:"displayName,omitempty"` - BasePath *HttpPath `json:"basePath,omitempty"` - Endpoints []*HttpEndpoint `json:"endpoints,omitempty"` - Headers []*HttpHeader `json:"headers,omitempty"` - PathParameters []*PathParameter `json:"pathParameters,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *DeclaredServiceName `json:"name,omitempty" url:"name,omitempty"` + DisplayName *string `json:"displayName,omitempty" url:"displayName,omitempty"` + BasePath *HttpPath `json:"basePath,omitempty" url:"basePath,omitempty"` + Endpoints []*HttpEndpoint `json:"endpoints,omitempty" url:"endpoints,omitempty"` + Headers []*HttpHeader `json:"headers,omitempty" url:"headers,omitempty"` + PathParameters []*PathParameter `json:"pathParameters,omitempty" url:"pathParameters,omitempty"` } func (h *HttpService) String() string { @@ -1425,10 +1546,10 @@ func (h *HttpService) String() string { } type InlinedRequestBody struct { - Name *Name `json:"name,omitempty"` - Extends []*DeclaredTypeName `json:"extends,omitempty"` - Properties []*InlinedRequestBodyProperty `json:"properties,omitempty"` - ContentType *string `json:"contentType,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Extends []*DeclaredTypeName `json:"extends,omitempty" url:"extends,omitempty"` + Properties []*InlinedRequestBodyProperty `json:"properties,omitempty" url:"properties,omitempty"` + ContentType *string `json:"contentType,omitempty" url:"contentType,omitempty"` } func (i *InlinedRequestBody) String() string { @@ -1439,9 +1560,9 @@ func (i *InlinedRequestBody) String() string { } type InlinedRequestBodyProperty struct { - Docs *string `json:"docs,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` } func (i *InlinedRequestBodyProperty) String() string { @@ -1532,8 +1653,8 @@ func (j *JsonResponse) Accept(visitor JsonResponseVisitor) error { } type JsonResponseBody struct { - Docs *string `json:"docs,omitempty"` - ResponseBodyType *TypeReference `json:"responseBodyType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + ResponseBodyType *TypeReference `json:"responseBodyType,omitempty" url:"responseBodyType,omitempty"` } func (j *JsonResponseBody) String() string { @@ -1544,14 +1665,14 @@ func (j *JsonResponseBody) String() string { } type JsonResponseBodyWithProperty struct { - Docs *string `json:"docs,omitempty"` - ResponseBodyType *TypeReference `json:"responseBodyType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + ResponseBodyType *TypeReference `json:"responseBodyType,omitempty" url:"responseBodyType,omitempty"` // If set, the SDK will return this property from // the response, rather than the response itself. // // This is particularly useful for JSON API structures // (e.g. configure 'data' to return 'response.data'). - ResponseProperty *ObjectProperty `json:"responseProperty,omitempty"` + ResponseProperty *ObjectProperty `json:"responseProperty,omitempty" url:"responseProperty,omitempty"` } func (j *JsonResponseBodyWithProperty) String() string { @@ -1562,11 +1683,11 @@ func (j *JsonResponseBodyWithProperty) String() string { } type PathParameter struct { - Docs *string `json:"docs,omitempty"` - Name *Name `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` - Location PathParameterLocation `json:"location,omitempty"` - Variable *VariableId `json:"variable,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` + Location PathParameterLocation `json:"location,omitempty" url:"location,omitempty"` + Variable *VariableId `json:"variable,omitempty" url:"variable,omitempty"` } func (p *PathParameter) String() string { @@ -1602,11 +1723,11 @@ func (p PathParameterLocation) Ptr() *PathParameterLocation { } type QueryParameter struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` - AllowMultiple bool `json:"allowMultiple"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` + AllowMultiple bool `json:"allowMultiple" url:"allowMultiple"` } func (q *QueryParameter) String() string { @@ -1617,8 +1738,8 @@ func (q *QueryParameter) String() string { } type ResponseError struct { - Docs *string `json:"docs,omitempty"` - Error *DeclaredErrorName `json:"error,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Error *DeclaredErrorName `json:"error,omitempty" url:"error,omitempty"` } func (r *ResponseError) String() string { @@ -1631,8 +1752,8 @@ func (r *ResponseError) String() string { type ResponseErrors = []*ResponseError type SdkRequest struct { - RequestParameterName *Name `json:"requestParameterName,omitempty"` - Shape *SdkRequestShape `json:"shape,omitempty"` + RequestParameterName *Name `json:"requestParameterName,omitempty" url:"requestParameterName,omitempty"` + Shape *SdkRequestShape `json:"shape,omitempty" url:"shape,omitempty"` } func (s *SdkRequest) String() string { @@ -1805,8 +1926,8 @@ func (s *SdkRequestShape) Accept(visitor SdkRequestShapeVisitor) error { } type SdkRequestWrapper struct { - WrapperName *Name `json:"wrapperName,omitempty"` - BodyKey *Name `json:"bodyKey,omitempty"` + WrapperName *Name `json:"wrapperName,omitempty" url:"wrapperName,omitempty"` + BodyKey *Name `json:"bodyKey,omitempty" url:"bodyKey,omitempty"` } func (s *SdkRequestWrapper) String() string { @@ -1817,9 +1938,9 @@ func (s *SdkRequestWrapper) String() string { } type StreamingResponse struct { - Docs *string `json:"docs,omitempty"` - DataEventType *StreamingResponseChunkType `json:"dataEventType,omitempty"` - Terminator *string `json:"terminator,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + DataEventType *StreamingResponseChunkType `json:"dataEventType,omitempty" url:"dataEventType,omitempty"` + Terminator *string `json:"terminator,omitempty" url:"terminator,omitempty"` } func (s *StreamingResponse) String() string { @@ -1911,8 +2032,48 @@ func (s *StreamingResponseChunkType) Accept(visitor StreamingResponseChunkTypeVi } } +type SupportedSdkLanguage string + +const ( + SupportedSdkLanguageCurl SupportedSdkLanguage = "curl" + SupportedSdkLanguagePython SupportedSdkLanguage = "python" + SupportedSdkLanguageJavascript SupportedSdkLanguage = "javascript" + SupportedSdkLanguageTypescript SupportedSdkLanguage = "typescript" + SupportedSdkLanguageGo SupportedSdkLanguage = "go" + SupportedSdkLanguageRuby SupportedSdkLanguage = "ruby" + SupportedSdkLanguageCsharp SupportedSdkLanguage = "csharp" + SupportedSdkLanguageJava SupportedSdkLanguage = "java" +) + +func NewSupportedSdkLanguageFromString(s string) (SupportedSdkLanguage, error) { + switch s { + case "curl": + return SupportedSdkLanguageCurl, nil + case "python": + return SupportedSdkLanguagePython, nil + case "javascript": + return SupportedSdkLanguageJavascript, nil + case "typescript": + return SupportedSdkLanguageTypescript, nil + case "go": + return SupportedSdkLanguageGo, nil + case "ruby": + return SupportedSdkLanguageRuby, nil + case "csharp": + return SupportedSdkLanguageCsharp, nil + case "java": + return SupportedSdkLanguageJava, nil + } + var t SupportedSdkLanguage + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (s SupportedSdkLanguage) Ptr() *SupportedSdkLanguage { + return &s +} + type TextResponse struct { - Docs *string `json:"docs,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` } func (t *TextResponse) String() string { @@ -1923,8 +2084,8 @@ func (t *TextResponse) String() string { } type ErrorDiscriminationByPropertyStrategy struct { - Discriminant *NameAndWireValue `json:"discriminant,omitempty"` - ContentProperty *NameAndWireValue `json:"contentProperty,omitempty"` + Discriminant *NameAndWireValue `json:"discriminant,omitempty" url:"discriminant,omitempty"` + ContentProperty *NameAndWireValue `json:"contentProperty,omitempty" url:"contentProperty,omitempty"` } func (e *ErrorDiscriminationByPropertyStrategy) String() string { @@ -2017,31 +2178,33 @@ func (e *ErrorDiscriminationStrategy) Accept(visitor ErrorDiscriminationStrategy // Complete representation of the API schema type IntermediateRepresentation struct { // This is the human readable unique id for the API. - ApiName *Name `json:"apiName,omitempty"` - ApiDisplayName *string `json:"apiDisplayName,omitempty"` - ApiDocs *string `json:"apiDocs,omitempty"` - Auth *ApiAuth `json:"auth,omitempty"` + ApiName *Name `json:"apiName,omitempty" url:"apiName,omitempty"` + ApiDisplayName *string `json:"apiDisplayName,omitempty" url:"apiDisplayName,omitempty"` + ApiDocs *string `json:"apiDocs,omitempty" url:"apiDocs,omitempty"` + Auth *ApiAuth `json:"auth,omitempty" url:"auth,omitempty"` // API Wide headers that are sent on every request - Headers []*HttpHeader `json:"headers,omitempty"` + Headers []*HttpHeader `json:"headers,omitempty" url:"headers,omitempty"` // Headers that are sent for idempotent endpoints - IdempotencyHeaders []*HttpHeader `json:"idempotencyHeaders,omitempty"` + IdempotencyHeaders []*HttpHeader `json:"idempotencyHeaders,omitempty" url:"idempotencyHeaders,omitempty"` // The types described by this API - Types map[TypeId]*TypeDeclaration `json:"types,omitempty"` + Types map[TypeId]*TypeDeclaration `json:"types,omitempty" url:"types,omitempty"` // The services exposed by this API - Services map[ServiceId]*HttpService `json:"services,omitempty"` + Services map[ServiceId]*HttpService `json:"services,omitempty" url:"services,omitempty"` // The webhooks sent by this API - WebhookGroups map[WebhookGroupId]WebhookGroup `json:"webhookGroups,omitempty"` - Errors map[ErrorId]*ErrorDeclaration `json:"errors,omitempty"` - Subpackages map[SubpackageId]*Subpackage `json:"subpackages,omitempty"` - RootPackage *Package `json:"rootPackage,omitempty"` - Constants *Constants `json:"constants,omitempty"` - Environments *EnvironmentsConfig `json:"environments,omitempty"` - BasePath *HttpPath `json:"basePath,omitempty"` - PathParameters []*PathParameter `json:"pathParameters,omitempty"` - ErrorDiscriminationStrategy *ErrorDiscriminationStrategy `json:"errorDiscriminationStrategy,omitempty"` - SdkConfig *SdkConfig `json:"sdkConfig,omitempty"` - Variables []*VariableDeclaration `json:"variables,omitempty"` - ServiceTypeReferenceInfo *ServiceTypeReferenceInfo `json:"serviceTypeReferenceInfo,omitempty"` + WebhookGroups map[WebhookGroupId]WebhookGroup `json:"webhookGroups,omitempty" url:"webhookGroups,omitempty"` + // The websocket channels served by this API + WebsocketChannels map[WebsocketChannelId]*WebsocketChannel `json:"websocketChannels,omitempty" url:"websocketChannels,omitempty"` + Errors map[ErrorId]*ErrorDeclaration `json:"errors,omitempty" url:"errors,omitempty"` + Subpackages map[SubpackageId]*Subpackage `json:"subpackages,omitempty" url:"subpackages,omitempty"` + RootPackage *Package `json:"rootPackage,omitempty" url:"rootPackage,omitempty"` + Constants *Constants `json:"constants,omitempty" url:"constants,omitempty"` + Environments *EnvironmentsConfig `json:"environments,omitempty" url:"environments,omitempty"` + BasePath *HttpPath `json:"basePath,omitempty" url:"basePath,omitempty"` + PathParameters []*PathParameter `json:"pathParameters,omitempty" url:"pathParameters,omitempty"` + ErrorDiscriminationStrategy *ErrorDiscriminationStrategy `json:"errorDiscriminationStrategy,omitempty" url:"errorDiscriminationStrategy,omitempty"` + SdkConfig *SdkConfig `json:"sdkConfig,omitempty" url:"sdkConfig,omitempty"` + Variables []*VariableDeclaration `json:"variables,omitempty" url:"variables,omitempty"` + ServiceTypeReferenceInfo *ServiceTypeReferenceInfo `json:"serviceTypeReferenceInfo,omitempty" url:"serviceTypeReferenceInfo,omitempty"` } func (i *IntermediateRepresentation) String() string { @@ -2052,15 +2215,16 @@ func (i *IntermediateRepresentation) String() string { } type Package struct { - Docs *string `json:"docs,omitempty"` - FernFilepath *FernFilepath `json:"fernFilepath,omitempty"` - Service *ServiceId `json:"service,omitempty"` - Types []TypeId `json:"types,omitempty"` - Errors []ErrorId `json:"errors,omitempty"` - Webhooks *WebhookGroupId `json:"webhooks,omitempty"` - Subpackages []SubpackageId `json:"subpackages,omitempty"` - HasEndpointsInTree bool `json:"hasEndpointsInTree"` - NavigationConfig *PackageNavigationConfig `json:"navigationConfig,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` + Service *ServiceId `json:"service,omitempty" url:"service,omitempty"` + Types []TypeId `json:"types,omitempty" url:"types,omitempty"` + Errors []ErrorId `json:"errors,omitempty" url:"errors,omitempty"` + Webhooks *WebhookGroupId `json:"webhooks,omitempty" url:"webhooks,omitempty"` + Websocket *WebsocketChannelId `json:"websocket,omitempty" url:"websocket,omitempty"` + Subpackages []SubpackageId `json:"subpackages,omitempty" url:"subpackages,omitempty"` + HasEndpointsInTree bool `json:"hasEndpointsInTree" url:"hasEndpointsInTree"` + NavigationConfig *PackageNavigationConfig `json:"navigationConfig,omitempty" url:"navigationConfig,omitempty"` } func (p *Package) String() string { @@ -2071,7 +2235,7 @@ func (p *Package) String() string { } type PackageNavigationConfig struct { - PointsTo SubpackageId `json:"pointsTo"` + PointsTo SubpackageId `json:"pointsTo" url:"pointsTo"` } func (p *PackageNavigationConfig) String() string { @@ -2082,9 +2246,9 @@ func (p *PackageNavigationConfig) String() string { } type PlatformHeaders struct { - Language string `json:"language"` - SdkName string `json:"sdkName"` - SdkVersion string `json:"sdkVersion"` + Language string `json:"language" url:"language"` + SdkName string `json:"sdkName" url:"sdkName"` + SdkVersion string `json:"sdkVersion" url:"sdkVersion"` } func (p *PlatformHeaders) String() string { @@ -2095,10 +2259,10 @@ func (p *PlatformHeaders) String() string { } type SdkConfig struct { - IsAuthMandatory bool `json:"isAuthMandatory"` - HasStreamingEndpoints bool `json:"hasStreamingEndpoints"` - HasFileDownloadEndpoints bool `json:"hasFileDownloadEndpoints"` - PlatformHeaders *PlatformHeaders `json:"platformHeaders,omitempty"` + IsAuthMandatory bool `json:"isAuthMandatory" url:"isAuthMandatory"` + HasStreamingEndpoints bool `json:"hasStreamingEndpoints" url:"hasStreamingEndpoints"` + HasFileDownloadEndpoints bool `json:"hasFileDownloadEndpoints" url:"hasFileDownloadEndpoints"` + PlatformHeaders *PlatformHeaders `json:"platformHeaders,omitempty" url:"platformHeaders,omitempty"` } func (s *SdkConfig) String() string { @@ -2110,9 +2274,9 @@ func (s *SdkConfig) String() string { type ServiceTypeReferenceInfo struct { // Types referenced by exactly one service. - TypesReferencedOnlyByService map[ServiceId][]TypeId `json:"typesReferencedOnlyByService,omitempty"` + TypesReferencedOnlyByService map[ServiceId][]TypeId `json:"typesReferencedOnlyByService,omitempty" url:"typesReferencedOnlyByService,omitempty"` // Types referenced by either zero or multiple services. - SharedTypes []TypeId `json:"sharedTypes,omitempty"` + SharedTypes []TypeId `json:"sharedTypes,omitempty" url:"sharedTypes,omitempty"` } func (s *ServiceTypeReferenceInfo) String() string { @@ -2123,16 +2287,17 @@ func (s *ServiceTypeReferenceInfo) String() string { } type Subpackage struct { - Docs *string `json:"docs,omitempty"` - FernFilepath *FernFilepath `json:"fernFilepath,omitempty"` - Service *ServiceId `json:"service,omitempty"` - Types []TypeId `json:"types,omitempty"` - Errors []ErrorId `json:"errors,omitempty"` - Webhooks *WebhookGroupId `json:"webhooks,omitempty"` - Subpackages []SubpackageId `json:"subpackages,omitempty"` - HasEndpointsInTree bool `json:"hasEndpointsInTree"` - NavigationConfig *PackageNavigationConfig `json:"navigationConfig,omitempty"` - Name *Name `json:"name,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` + Service *ServiceId `json:"service,omitempty" url:"service,omitempty"` + Types []TypeId `json:"types,omitempty" url:"types,omitempty"` + Errors []ErrorId `json:"errors,omitempty" url:"errors,omitempty"` + Webhooks *WebhookGroupId `json:"webhooks,omitempty" url:"webhooks,omitempty"` + Websocket *WebsocketChannelId `json:"websocket,omitempty" url:"websocket,omitempty"` + Subpackages []SubpackageId `json:"subpackages,omitempty" url:"subpackages,omitempty"` + HasEndpointsInTree bool `json:"hasEndpointsInTree" url:"hasEndpointsInTree"` + NavigationConfig *PackageNavigationConfig `json:"navigationConfig,omitempty" url:"navigationConfig,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` } func (s *Subpackage) String() string { @@ -2143,8 +2308,8 @@ func (s *Subpackage) String() string { } type AliasTypeDeclaration struct { - AliasOf *TypeReference `json:"aliasOf,omitempty"` - ResolvedType *ResolvedTypeReference `json:"resolvedType,omitempty"` + AliasOf *TypeReference `json:"aliasOf,omitempty" url:"aliasOf,omitempty"` + ResolvedType *ResolvedTypeReference `json:"resolvedType,omitempty" url:"resolvedType,omitempty"` } func (a *AliasTypeDeclaration) String() string { @@ -2312,9 +2477,9 @@ func (c *ContainerType) Accept(visitor ContainerTypeVisitor) error { } type DeclaredTypeName struct { - TypeId TypeId `json:"typeId"` - FernFilepath *FernFilepath `json:"fernFilepath,omitempty"` - Name *Name `json:"name,omitempty"` + TypeId TypeId `json:"typeId" url:"typeId"` + FernFilepath *FernFilepath `json:"fernFilepath,omitempty" url:"fernFilepath,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` } func (d *DeclaredTypeName) String() string { @@ -2325,7 +2490,7 @@ func (d *DeclaredTypeName) String() string { } type EnumTypeDeclaration struct { - Values []*EnumValue `json:"values,omitempty"` + Values []*EnumValue `json:"values,omitempty" url:"values,omitempty"` } func (e *EnumTypeDeclaration) String() string { @@ -2336,9 +2501,9 @@ func (e *EnumTypeDeclaration) String() string { } type EnumValue struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` } func (e *EnumValue) String() string { @@ -2349,7 +2514,7 @@ func (e *EnumValue) String() string { } type ExampleAliasType struct { - Value *ExampleTypeReference `json:"value,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` } func (e *ExampleAliasType) String() string { @@ -2494,7 +2659,7 @@ func (e *ExampleContainer) Accept(visitor ExampleContainerVisitor) error { } type ExampleEnumType struct { - Value *NameAndWireValue `json:"value,omitempty"` + Value *NameAndWireValue `json:"value,omitempty" url:"value,omitempty"` } func (e *ExampleEnumType) String() string { @@ -2505,8 +2670,8 @@ func (e *ExampleEnumType) String() string { } type ExampleKeyValuePair struct { - Key *ExampleTypeReference `json:"key,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Key *ExampleTypeReference `json:"key,omitempty" url:"key,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` } func (e *ExampleKeyValuePair) String() string { @@ -2517,8 +2682,8 @@ func (e *ExampleKeyValuePair) String() string { } type ExampleNamedType struct { - TypeName *DeclaredTypeName `json:"typeName,omitempty"` - Shape *ExampleTypeShape `json:"shape,omitempty"` + TypeName *DeclaredTypeName `json:"typeName,omitempty" url:"typeName,omitempty"` + Shape *ExampleTypeShape `json:"shape,omitempty" url:"shape,omitempty"` } func (e *ExampleNamedType) String() string { @@ -2529,11 +2694,11 @@ func (e *ExampleNamedType) String() string { } type ExampleObjectProperty struct { - Name *NameAndWireValue `json:"name,omitempty"` - Value *ExampleTypeReference `json:"value,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + Value *ExampleTypeReference `json:"value,omitempty" url:"value,omitempty"` // This property may have been brought in via extension. originalTypeDeclaration // is the name of the type that contains this property. - OriginalTypeDeclaration *DeclaredTypeName `json:"originalTypeDeclaration,omitempty"` + OriginalTypeDeclaration *DeclaredTypeName `json:"originalTypeDeclaration,omitempty" url:"originalTypeDeclaration,omitempty"` } func (e *ExampleObjectProperty) String() string { @@ -2544,7 +2709,7 @@ func (e *ExampleObjectProperty) String() string { } type ExampleObjectType struct { - Properties []*ExampleObjectProperty `json:"properties,omitempty"` + Properties []*ExampleObjectProperty `json:"properties,omitempty" url:"properties,omitempty"` } func (e *ExampleObjectType) String() string { @@ -2555,8 +2720,8 @@ func (e *ExampleObjectType) String() string { } type ExampleObjectTypeWithTypeId struct { - TypeId TypeId `json:"typeId"` - Object *ExampleObjectType `json:"object,omitempty"` + TypeId TypeId `json:"typeId" url:"typeId"` + Object *ExampleObjectType `json:"object,omitempty" url:"object,omitempty"` } func (e *ExampleObjectTypeWithTypeId) String() string { @@ -2661,20 +2826,20 @@ func (e *ExamplePrimitive) UnmarshalJSON(data []byte) error { e.Long = valueUnmarshaler.Long case "datetime": var valueUnmarshaler struct { - Datetime time.Time `json:"datetime"` + Datetime *core.DateTime `json:"datetime"` } if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { return err } - e.Datetime = valueUnmarshaler.Datetime + e.Datetime = valueUnmarshaler.Datetime.Time() case "date": var valueUnmarshaler struct { - Date time.Time `json:"date"` + Date *core.Date `json:"date" format:"date"` } if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { return err } - e.Date = valueUnmarshaler.Date + e.Date = valueUnmarshaler.Date.Time() case "uuid": var valueUnmarshaler struct { Uuid uuid.UUID `json:"uuid"` @@ -2738,20 +2903,20 @@ func (e ExamplePrimitive) MarshalJSON() ([]byte, error) { return json.Marshal(marshaler) case "datetime": var marshaler = struct { - Type string `json:"type"` - Datetime time.Time `json:"datetime"` + Type string `json:"type"` + Datetime *core.DateTime `json:"datetime"` }{ Type: e.Type, - Datetime: e.Datetime, + Datetime: core.NewDateTime(e.Datetime), } return json.Marshal(marshaler) case "date": var marshaler = struct { - Type string `json:"type"` - Date time.Time `json:"date"` + Type string `json:"type"` + Date *core.Date `json:"date" format:"date"` }{ Type: e.Type, - Date: e.Date, + Date: core.NewDate(e.Date), } return json.Marshal(marshaler) case "uuid": @@ -2801,8 +2966,8 @@ func (e *ExamplePrimitive) Accept(visitor ExamplePrimitiveVisitor) error { } type ExampleSingleUnionType struct { - WireDiscriminantValue *NameAndWireValue `json:"wireDiscriminantValue,omitempty"` - Shape *ExampleSingleUnionTypeProperties `json:"shape,omitempty"` + WireDiscriminantValue *NameAndWireValue `json:"wireDiscriminantValue,omitempty" url:"wireDiscriminantValue,omitempty"` + Shape *ExampleSingleUnionTypeProperties `json:"shape,omitempty" url:"shape,omitempty"` } func (e *ExampleSingleUnionType) String() string { @@ -2916,10 +3081,10 @@ func (e *ExampleSingleUnionTypeProperties) Accept(visitor ExampleSingleUnionType } type ExampleType struct { - JsonExample interface{} `json:"jsonExample,omitempty"` - Docs *string `json:"docs,omitempty"` - Name *Name `json:"name,omitempty"` - Shape *ExampleTypeShape `json:"shape,omitempty"` + JsonExample interface{} `json:"jsonExample,omitempty" url:"jsonExample,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Shape *ExampleTypeShape `json:"shape,omitempty" url:"shape,omitempty"` } func (e *ExampleType) String() string { @@ -2930,8 +3095,8 @@ func (e *ExampleType) String() string { } type ExampleTypeReference struct { - JsonExample interface{} `json:"jsonExample,omitempty"` - Shape *ExampleTypeReferenceShape `json:"shape,omitempty"` + JsonExample interface{} `json:"jsonExample,omitempty" url:"jsonExample,omitempty"` + Shape *ExampleTypeReferenceShape `json:"shape,omitempty" url:"shape,omitempty"` } func (e *ExampleTypeReference) String() string { @@ -3238,8 +3403,8 @@ type ExampleUndiscriminatedUnionType struct { // // a string example would have an index 0 and an integer example // would have an index 1. - Index int `json:"index"` - SingleUnionType *ExampleTypeReference `json:"singleUnionType,omitempty"` + Index int `json:"index" url:"index"` + SingleUnionType *ExampleTypeReference `json:"singleUnionType,omitempty" url:"singleUnionType,omitempty"` } func (e *ExampleUndiscriminatedUnionType) String() string { @@ -3250,8 +3415,8 @@ func (e *ExampleUndiscriminatedUnionType) String() string { } type ExampleUnionType struct { - Discriminant *NameAndWireValue `json:"discriminant,omitempty"` - SingleUnionType *ExampleSingleUnionType `json:"singleUnionType,omitempty"` + Discriminant *NameAndWireValue `json:"discriminant,omitempty" url:"discriminant,omitempty"` + SingleUnionType *ExampleSingleUnionType `json:"singleUnionType,omitempty" url:"singleUnionType,omitempty"` } func (e *ExampleUnionType) String() string { @@ -3346,8 +3511,8 @@ func (l *Literal) Accept(visitor LiteralVisitor) error { } type MapType struct { - KeyType *TypeReference `json:"keyType,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` + KeyType *TypeReference `json:"keyType,omitempty" url:"keyType,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` } func (m *MapType) String() string { @@ -3358,10 +3523,10 @@ func (m *MapType) String() string { } type ObjectProperty struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` } func (o *ObjectProperty) String() string { @@ -3373,8 +3538,8 @@ func (o *ObjectProperty) String() string { type ObjectTypeDeclaration struct { // A list of other types to inherit from - Extends []*DeclaredTypeName `json:"extends,omitempty"` - Properties []*ObjectProperty `json:"properties,omitempty"` + Extends []*DeclaredTypeName `json:"extends,omitempty" url:"extends,omitempty"` + Properties []*ObjectProperty `json:"properties,omitempty" url:"properties,omitempty"` } func (o *ObjectTypeDeclaration) String() string { @@ -3429,8 +3594,8 @@ func (p PrimitiveType) Ptr() *PrimitiveType { } type ResolvedNamedType struct { - Name *DeclaredTypeName `json:"name,omitempty"` - Shape ShapeType `json:"shape,omitempty"` + Name *DeclaredTypeName `json:"name,omitempty" url:"name,omitempty"` + Shape ShapeType `json:"shape,omitempty" url:"shape,omitempty"` } func (r *ResolvedNamedType) String() string { @@ -3599,9 +3764,9 @@ func (s ShapeType) Ptr() *ShapeType { } type SingleUnionType struct { - Docs *string `json:"docs,omitempty"` - DiscriminantValue *NameAndWireValue `json:"discriminantValue,omitempty"` - Shape *SingleUnionTypeProperties `json:"shape,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + DiscriminantValue *NameAndWireValue `json:"discriminantValue,omitempty" url:"discriminantValue,omitempty"` + Shape *SingleUnionTypeProperties `json:"shape,omitempty" url:"shape,omitempty"` } func (s *SingleUnionType) String() string { @@ -3715,8 +3880,8 @@ func (s *SingleUnionTypeProperties) Accept(visitor SingleUnionTypePropertiesVisi } type SingleUnionTypeProperty struct { - Name *NameAndWireValue `json:"name,omitempty"` - Type *TypeReference `json:"type,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` } func (s *SingleUnionTypeProperty) String() string { @@ -3877,13 +4042,13 @@ func (t *Type) Accept(visitor TypeVisitor) error { // A type, which is a name and a shape type TypeDeclaration struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name *DeclaredTypeName `json:"name,omitempty"` - Shape *Type `json:"shape,omitempty"` - Examples []*ExampleType `json:"examples,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name *DeclaredTypeName `json:"name,omitempty" url:"name,omitempty"` + Shape *Type `json:"shape,omitempty" url:"shape,omitempty"` + Examples []*ExampleType `json:"examples,omitempty" url:"examples,omitempty"` // All other named types that this type references (directly or indirectly) - ReferencedTypes []TypeId `json:"referencedTypes,omitempty"` + ReferencedTypes []TypeId `json:"referencedTypes,omitempty" url:"referencedTypes,omitempty"` } func (t *TypeDeclaration) String() string { @@ -4024,8 +4189,8 @@ func (t *TypeReference) Accept(visitor TypeReferenceVisitor) error { } type UndiscriminatedUnionMember struct { - Docs *string `json:"docs,omitempty"` - Type *TypeReference `json:"type,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` } func (u *UndiscriminatedUnionMember) String() string { @@ -4036,7 +4201,7 @@ func (u *UndiscriminatedUnionMember) String() string { } type UndiscriminatedUnionTypeDeclaration struct { - Members []*UndiscriminatedUnionMember `json:"members,omitempty"` + Members []*UndiscriminatedUnionMember `json:"members,omitempty" url:"members,omitempty"` } func (u *UndiscriminatedUnionTypeDeclaration) String() string { @@ -4047,11 +4212,11 @@ func (u *UndiscriminatedUnionTypeDeclaration) String() string { } type UnionTypeDeclaration struct { - Discriminant *NameAndWireValue `json:"discriminant,omitempty"` + Discriminant *NameAndWireValue `json:"discriminant,omitempty" url:"discriminant,omitempty"` // A list of other types to inherit from - Extends []*DeclaredTypeName `json:"extends,omitempty"` - Types []*SingleUnionType `json:"types,omitempty"` - BaseProperties []*ObjectProperty `json:"baseProperties,omitempty"` + Extends []*DeclaredTypeName `json:"extends,omitempty" url:"extends,omitempty"` + Types []*SingleUnionType `json:"types,omitempty" url:"types,omitempty"` + BaseProperties []*ObjectProperty `json:"baseProperties,omitempty" url:"baseProperties,omitempty"` } func (u *UnionTypeDeclaration) String() string { @@ -4062,10 +4227,10 @@ func (u *UnionTypeDeclaration) String() string { } type VariableDeclaration struct { - Docs *string `json:"docs,omitempty"` - Id VariableId `json:"id"` - Name *Name `json:"name,omitempty"` - Type *TypeReference `json:"type,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Id VariableId `json:"id" url:"id"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Type *TypeReference `json:"type,omitempty" url:"type,omitempty"` } func (v *VariableDeclaration) String() string { @@ -4078,9 +4243,9 @@ func (v *VariableDeclaration) String() string { type VariableId = string type InlinedWebhookPayload struct { - Name *Name `json:"name,omitempty"` - Extends []*DeclaredTypeName `json:"extends,omitempty"` - Properties []*InlinedWebhookPayloadProperty `json:"properties,omitempty"` + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Extends []*DeclaredTypeName `json:"extends,omitempty" url:"extends,omitempty"` + Properties []*InlinedWebhookPayloadProperty `json:"properties,omitempty" url:"properties,omitempty"` } func (i *InlinedWebhookPayload) String() string { @@ -4091,9 +4256,9 @@ func (i *InlinedWebhookPayload) String() string { } type InlinedWebhookPayloadProperty struct { - Docs *string `json:"docs,omitempty"` - Name *NameAndWireValue `json:"name,omitempty"` - ValueType *TypeReference `json:"valueType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` } func (i *InlinedWebhookPayloadProperty) String() string { @@ -4104,13 +4269,13 @@ func (i *InlinedWebhookPayloadProperty) String() string { } type Webhook struct { - Docs *string `json:"docs,omitempty"` - Availability *Availability `json:"availability,omitempty"` - Name WebhookName `json:"name,omitempty"` - DisplayName *string `json:"displayName,omitempty"` - Method WebhookHttpMethod `json:"method,omitempty"` - Headers []*HttpHeader `json:"headers,omitempty"` - Payload *WebhookPayload `json:"payload,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Name WebhookName `json:"name,omitempty" url:"name,omitempty"` + DisplayName *string `json:"displayName,omitempty" url:"displayName,omitempty"` + Method WebhookHttpMethod `json:"method,omitempty" url:"method,omitempty"` + Headers []*HttpHeader `json:"headers,omitempty" url:"headers,omitempty"` + Payload *WebhookPayload `json:"payload,omitempty" url:"payload,omitempty"` } func (w *Webhook) String() string { @@ -4227,8 +4392,8 @@ func (w *WebhookPayload) Accept(visitor WebhookPayloadVisitor) error { } type WebhookPayloadReference struct { - Docs *string `json:"docs,omitempty"` - PayloadType *TypeReference `json:"payloadType,omitempty"` + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + PayloadType *TypeReference `json:"payloadType,omitempty" url:"payloadType,omitempty"` } func (w *WebhookPayloadReference) String() string { @@ -4237,3 +4402,179 @@ func (w *WebhookPayloadReference) String() string { } return fmt.Sprintf("%#v", w) } + +type InlinedWebsocketMessageBody struct { + Name *Name `json:"name,omitempty" url:"name,omitempty"` + Extends []*DeclaredTypeName `json:"extends,omitempty" url:"extends,omitempty"` + Properties []*InlinedWebsocketMessageBodyProperty `json:"properties,omitempty" url:"properties,omitempty"` +} + +func (i *InlinedWebsocketMessageBody) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type InlinedWebsocketMessageBodyProperty struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Name *NameAndWireValue `json:"name,omitempty" url:"name,omitempty"` + ValueType *TypeReference `json:"valueType,omitempty" url:"valueType,omitempty"` +} + +func (i *InlinedWebsocketMessageBodyProperty) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type WebsocketChannel struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + Path *HttpPath `json:"path,omitempty" url:"path,omitempty"` + Auth bool `json:"auth" url:"auth"` + Headers []*HttpHeader `json:"headers,omitempty" url:"headers,omitempty"` + QueryParameters []*QueryParameter `json:"queryParameters,omitempty" url:"queryParameters,omitempty"` + PathParameters []*PathParameter `json:"pathParameters,omitempty" url:"pathParameters,omitempty"` + // The messages that can be sent and received on this channel + Messages map[WebsocketMessageId]*WebsocketMessage `json:"messages,omitempty" url:"messages,omitempty"` +} + +func (w *WebsocketChannel) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} + +type WebsocketMessage struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + Availability *Availability `json:"availability,omitempty" url:"availability,omitempty"` + DisplayName *string `json:"displayName,omitempty" url:"displayName,omitempty"` + Origin WebsocketMessageOrigin `json:"origin,omitempty" url:"origin,omitempty"` + Body *WebsocketMessageBody `json:"body,omitempty" url:"body,omitempty"` +} + +func (w *WebsocketMessage) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} + +type WebsocketMessageBody struct { + Type string + InlinedBody *InlinedWebsocketMessageBody + Reference *WebsocketMessageBodyReference +} + +func NewWebsocketMessageBodyFromInlinedBody(value *InlinedWebsocketMessageBody) *WebsocketMessageBody { + return &WebsocketMessageBody{Type: "inlinedBody", InlinedBody: value} +} + +func NewWebsocketMessageBodyFromReference(value *WebsocketMessageBodyReference) *WebsocketMessageBody { + return &WebsocketMessageBody{Type: "reference", Reference: value} +} + +func (w *WebsocketMessageBody) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + w.Type = unmarshaler.Type + switch unmarshaler.Type { + case "inlinedBody": + value := new(InlinedWebsocketMessageBody) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + w.InlinedBody = value + case "reference": + value := new(WebsocketMessageBodyReference) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + w.Reference = value + } + return nil +} + +func (w WebsocketMessageBody) MarshalJSON() ([]byte, error) { + switch w.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", w.Type, w) + case "inlinedBody": + var marshaler = struct { + Type string `json:"type"` + *InlinedWebsocketMessageBody + }{ + Type: w.Type, + InlinedWebsocketMessageBody: w.InlinedBody, + } + return json.Marshal(marshaler) + case "reference": + var marshaler = struct { + Type string `json:"type"` + *WebsocketMessageBodyReference + }{ + Type: w.Type, + WebsocketMessageBodyReference: w.Reference, + } + return json.Marshal(marshaler) + } +} + +type WebsocketMessageBodyVisitor interface { + VisitInlinedBody(*InlinedWebsocketMessageBody) error + VisitReference(*WebsocketMessageBodyReference) error +} + +func (w *WebsocketMessageBody) Accept(visitor WebsocketMessageBodyVisitor) error { + switch w.Type { + default: + return fmt.Errorf("invalid type %s in %T", w.Type, w) + case "inlinedBody": + return visitor.VisitInlinedBody(w.InlinedBody) + case "reference": + return visitor.VisitReference(w.Reference) + } +} + +type WebsocketMessageBodyReference struct { + Docs *string `json:"docs,omitempty" url:"docs,omitempty"` + BodyType *TypeReference `json:"bodyType,omitempty" url:"bodyType,omitempty"` +} + +func (w *WebsocketMessageBodyReference) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} + +type WebsocketMessageId = string + +type WebsocketMessageOrigin string + +const ( + WebsocketMessageOriginClient WebsocketMessageOrigin = "client" + WebsocketMessageOriginServer WebsocketMessageOrigin = "server" +) + +func NewWebsocketMessageOriginFromString(s string) (WebsocketMessageOrigin, error) { + switch s { + case "client": + return WebsocketMessageOriginClient, nil + case "server": + return WebsocketMessageOriginServer, nil + } + var t WebsocketMessageOrigin + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (w WebsocketMessageOrigin) Ptr() *WebsocketMessageOrigin { + return &w +} diff --git a/generators/go/internal/generator/config.go b/generators/go/internal/generator/config.go index f42eb5ae685..bc0297ff799 100644 --- a/generators/go/internal/generator/config.go +++ b/generators/go/internal/generator/config.go @@ -10,6 +10,7 @@ type Config struct { Organization string Version string IRFilepath string + SnippetFilepath string ImportPath string PackageName string @@ -43,6 +44,7 @@ func NewConfig( organization string, version string, irFilepath string, + snippetFilepath string, importPath string, packageName string, moduleConfig *ModuleConfig, @@ -56,6 +58,7 @@ func NewConfig( Whitelabel: whitelabel, Version: version, IRFilepath: irFilepath, + SnippetFilepath: snippetFilepath, ImportPath: importPath, PackageName: packageName, ModuleConfig: moduleConfig, diff --git a/generators/go/internal/generator/file_writer.go b/generators/go/internal/generator/file_writer.go index a6bc3972b8b..ca770547ffd 100644 --- a/generators/go/internal/generator/file_writer.go +++ b/generators/go/internal/generator/file_writer.go @@ -36,6 +36,7 @@ type fileWriter struct { types map[ir.TypeId]*ir.TypeDeclaration errors map[ir.ErrorId]*ir.ErrorDeclaration coordinator *coordinator.Client + snippetWriter *SnippetWriter buffer *bytes.Buffer } @@ -85,6 +86,7 @@ func newFileWriter( types: types, errors: errors, coordinator: coordinator, + snippetWriter: NewSnippetWriter(baseImportPath, types), buffer: new(bytes.Buffer), } } diff --git a/generators/go/internal/generator/generator.go b/generators/go/internal/generator/generator.go index ba1bced9383..71491403962 100644 --- a/generators/go/internal/generator/generator.go +++ b/generators/go/internal/generator/generator.go @@ -11,6 +11,7 @@ import ( "github.com/fern-api/fern-go/internal/ast" "github.com/fern-api/fern-go/internal/coordinator" + "github.com/fern-api/fern-go/internal/fern/ir" fernir "github.com/fern-api/fern-go/internal/fern/ir" generatorexec "github.com/fern-api/generator-exec-go" ) @@ -238,7 +239,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( if subpackage.Docs == nil || len(*subpackage.Docs) == 0 { continue } - fileInfo := fileInfoForPackage(rootPackageName, subpackage.FernFilepath) + fileInfo := fileInfoForPackageDocs(subpackage.FernFilepath) writer := newFileWriter(fileInfo.filename, fileInfo.packageName, "", g.config.Whitelabel, nil, nil, g.coordinator) writer.WriteDocs(subpackage.Docs) files = append(files, writer.DocsFile()) @@ -253,7 +254,10 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( files = append(files, newStringerFile(g.coordinator)) files = append(files, newTimeFile(g.coordinator)) // Then handle mode-specific generation tasks. - var generatedClient *GeneratedClient + var rootClientInstantiation *ast.AssignStmt + generatedRootClient := &GeneratedClient{ + Instantiation: rootClientInstantiation, + } switch mode { case ModeFiber: break @@ -330,6 +334,11 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( return nil, err } files = append(files, file) + rootClientInstantiation = generatedClientInstantiation( + g.config.ImportPath, + generatedAuth, + generatedEnvironment, + ) if len(ir.IdempotencyHeaders) > 0 { fileInfo = fileInfoForIdempotentRequestOptionsDefinition() writer = newFileWriter( @@ -461,30 +470,35 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( rootSubpackages = append(rootSubpackages, subpackage) } if ir.RootPackage.Service != nil { - file, generatedClient, err = g.generateService( + file, generatedClient, err := g.generateService( ir, ir.Services[*ir.RootPackage.Service], rootSubpackages, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, ir.RootPackage.FernFilepath, ) if err != nil { return nil, err } files = append(files, file) + + // Merge this client's endpoints with the root generated client. + generatedRootClient.Endpoints = append(generatedRootClient.Endpoints, generatedClient.Endpoints...) + } else { - file, generatedClient, err = g.generateRootServiceWithoutEndpoints( + file, generatedClient, err := g.generateRootServiceWithoutEndpoints( ir, ir.RootPackage.FernFilepath, rootSubpackages, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, ) if err != nil { return nil, err } files = append(files, file) + + // Merge this client's endpoints with the root generated client. + generatedRootClient.Endpoints = append(generatedRootClient.Endpoints, generatedClient.Endpoints...) } } // Then generate the client for all of the subpackages. @@ -512,8 +526,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( ir, irSubpackage, subpackages, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, subpackageToGenerate.OriginalFernFilepath, ) if err != nil { @@ -523,18 +536,26 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( continue } // This service has endpoints, so we proceed with the normal flow. - file, _, err := g.generateService( + file, generatedClient, err := g.generateService( ir, ir.Services[*irSubpackage.Service], subpackages, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, subpackageToGenerate.OriginalFernFilepath, ) if err != nil { return nil, err } files = append(files, file) + + // Merge this client's endpoints with the root generated client. + generatedRootClient.Endpoints = append(generatedRootClient.Endpoints, generatedClient.Endpoints...) + } + } + // Write the snippets, if any. + if g.config.SnippetFilepath != "" { + if err := maybeWriteSnippets(g.coordinator, generatedRootClient, g.config.SnippetFilepath); err != nil { + return nil, err } } // Finally, generate the go.mod file, if needed. @@ -549,8 +570,8 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( } files = append(files, file) - if g.config.IncludeReadme { - if err := g.generateReadme(generatedClient, generatedGoVersion); err != nil { + if g.config.IncludeReadme && generatedRootClient.Instantiation != nil { + if err := g.generateReadme(generatedRootClient, generatedGoVersion); err != nil { return nil, err } files = append(files, file) @@ -559,57 +580,11 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( return files, nil } -// generateReadme generates a README.md file for a generated Go module, called -// if a module config was provided. -// -// Parameters: -// - generatedClient: The generated client, if any. -// - generatedGoVersion: The Go version that the generated client supports. -func (g *Generator) generateReadme( - generatedClient *GeneratedClient, - generatedGoVersion string, -) (err error) { - badge := generatorexec.BadgeTypeGo - capitalizedOrganization := strings.Title(g.config.Organization) - - installation := fmt.Sprintf("Run the following command to use the %s Go library in your module:\n", capitalizedOrganization) - installation += "```sh\n" - installation += fmt.Sprintf("go get %s\n", g.config.ModuleConfig.Path) - installation += "```\n" - - var usage string - if generatedClient != nil { - usage, err = ast.NewSourceCodeBuilder(generatedClient.Instantiation).BuildSnippet() - if err != nil { - return err - } - usage = "```go\n" + usage + "\n```\n" - } - - return g.coordinator.GenerateReadme( - &generatorexec.GenerateReadmeRequest{ - Title: fmt.Sprintf("%s Go Library", capitalizedOrganization), - Badge: &badge, - Summary: fmt.Sprintf( - "The %s Go library provides convenient access to the %s API from Go.", - capitalizedOrganization, - capitalizedOrganization, - ), - Requirements: []string{ - fmt.Sprintf("Go version >= %s", generatedGoVersion), - }, - Installation: generatorexec.String(installation), - Usage: usage, - }, - ) -} - func (g *Generator) generateService( ir *fernir.IntermediateRepresentation, irService *fernir.HttpService, irSubpackages []*fernir.Subpackage, - generatedAuth *GeneratedAuth, - generatedEnvironment *GeneratedEnvironment, + rootClientInstantiation *ast.AssignStmt, originalFernFilepath *fernir.FernFilepath, ) (*File, *GeneratedClient, error) { fileInfo := fileInfoForService(irService.Name.FernFilepath) @@ -629,8 +604,7 @@ func (g *Generator) generateService( ir.Environments, ir.ErrorDiscriminationStrategy, originalFernFilepath, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, ) if err != nil { return nil, nil, err @@ -649,8 +623,7 @@ func (g *Generator) generateServiceWithoutEndpoints( ir *fernir.IntermediateRepresentation, irSubpackage *fernir.Subpackage, irSubpackages []*fernir.Subpackage, - generatedAuth *GeneratedAuth, - generatedEnvironment *GeneratedEnvironment, + rootClientInstantiation *ast.AssignStmt, originalFernFilepath *fernir.FernFilepath, ) (*File, error) { fileInfo := fileInfoForService(irSubpackage.FernFilepath) @@ -670,8 +643,7 @@ func (g *Generator) generateServiceWithoutEndpoints( nil, ir.ErrorDiscriminationStrategy, originalFernFilepath, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, ); err != nil { return nil, err } @@ -685,8 +657,7 @@ func (g *Generator) generateRootServiceWithoutEndpoints( ir *fernir.IntermediateRepresentation, fernFilepath *fernir.FernFilepath, irSubpackages []*fernir.Subpackage, - generatedAuth *GeneratedAuth, - generatedEnvironment *GeneratedEnvironment, + rootClientInstantiation *ast.AssignStmt, ) (*File, *GeneratedClient, error) { fileInfo := fileInfoForService(fernFilepath) writer := newFileWriter( @@ -705,8 +676,7 @@ func (g *Generator) generateRootServiceWithoutEndpoints( nil, ir.ErrorDiscriminationStrategy, fernFilepath, - generatedAuth, - generatedEnvironment, + rootClientInstantiation, ) if err != nil { return nil, nil, err @@ -718,6 +688,106 @@ func (g *Generator) generateRootServiceWithoutEndpoints( return file, generatedClient, nil } +func maybeWriteSnippets( + coordinator *coordinator.Client, + generatedClient *GeneratedClient, + snippetFilepath string, +) error { + if len(generatedClient.Endpoints) == 0 { + return nil + } + var endpoints []*generatorexec.Endpoint + for _, generatedEndpoint := range generatedClient.Endpoints { + client, err := ast.NewSourceCodeBuilder(generatedEndpoint.Snippet).BuildSnippet() + if err != nil { + // Log the warning and continue. We don't want to fail generation just + // because there's a bug in the snippet generator. + _ = coordinator.Log( + generatorexec.LogLevelWarn, + fmt.Sprintf( + "Failed to generate snippet for endpoint %s %q: %v", + generatedEndpoint.Identifier.Method, + generatedEndpoint.Identifier.Path, + err, + ), + ) + continue + } + endpoints = append( + endpoints, + &generatorexec.Endpoint{ + Id: generatedEndpoint.Identifier, + Snippet: generatorexec.NewEndpointSnippetFromGo( + &generatorexec.GoEndpointSnippet{ + Client: client, + }, + ), + }, + ) + } + // Sort the endpoints for deterministic results. + sort.Slice( + endpoints, + func(i, j int) bool { + return generatorexecEndpointSnippetToString(endpoints[i]) < generatorexecEndpointSnippetToString(endpoints[j]) + }, + ) + snippets := &generatorexec.Snippets{ + Types: make(map[ir.TypeId]string), + Endpoints: endpoints, + } + bytes, err := json.MarshalIndent(snippets, "", " ") + if err != nil { + return err + } + return os.WriteFile(snippetFilepath, bytes, 0644) +} + +// generateReadme generates a README.md file for a generated Go module, called +// if a module config was provided. +// +// Parameters: +// - generatedClient: The generated client, if any. +// - generatedGoVersion: The Go version that the generated client supports. +func (g *Generator) generateReadme( + generatedClient *GeneratedClient, + generatedGoVersion string, +) (err error) { + badge := generatorexec.BadgeTypeGo + capitalizedOrganization := strings.Title(g.config.Organization) + + installation := fmt.Sprintf("Run the following command to use the %s Go library in your module:\n", capitalizedOrganization) + installation += "```sh\n" + installation += fmt.Sprintf("go get %s\n", g.config.ModuleConfig.Path) + installation += "```\n" + + var usage string + if generatedClient != nil { + usage, err = ast.NewSourceCodeBuilder(generatedClient.Instantiation).BuildSnippet() + if err != nil { + return err + } + usage = "```go\n" + usage + "\n```\n" + } + + return g.coordinator.GenerateReadme( + &generatorexec.GenerateReadmeRequest{ + Title: fmt.Sprintf("%s Go Library", capitalizedOrganization), + Badge: &badge, + Summary: fmt.Sprintf( + "The %s Go library provides convenient access to the %s API from Go.", + capitalizedOrganization, + capitalizedOrganization, + ), + Requirements: []string{ + fmt.Sprintf("Go version >= %s", generatedGoVersion), + }, + Installation: generatorexec.String(installation), + Usage: usage, + }, + ) +} + // readIR reads the *InermediateRepresentation from the given filename. func readIR(irFilename string) (*fernir.IntermediateRepresentation, error) { bytes, err := os.ReadFile(irFilename) @@ -985,6 +1055,14 @@ func fileInfoForType(rootPackageName string, fernFilepath *fernir.FernFilepath) } } +func fileInfoForPackageDocs(fernFilepath *fernir.FernFilepath) *fileInfo { + packagePath := packagePathForDocs(fernFilepath) + return &fileInfo{ + filename: filepath.Join(append(packagePath, "doc.go")...), + packageName: packagePath[len(packagePath)-1], + } +} + func fileInfoForService(fernFilepath *fernir.FernFilepath) *fileInfo { packagePath := packagePathForClient(fernFilepath) return &fileInfo{ @@ -1065,6 +1143,18 @@ func fileUploadHasBodyProperties(fileUpload *fernir.FileUploadRequest) bool { return false } +func packagePathForDocs(fernFilepath *fernir.FernFilepath) []string { + var packages []string + for _, packageName := range fernFilepath.PackagePath { + packages = append(packages, strings.ToLower(packageName.CamelCase.SafeName)) + } + if fernFilepath.File == nil { + return packages + } + directory := strings.ToLower(fernFilepath.File.CamelCase.SafeName) + return append(packages, directory) +} + func packagePathForClient(fernFilepath *fernir.FernFilepath) []string { var packages []string for _, packageName := range fernFilepath.PackagePath { @@ -1300,6 +1390,27 @@ func getRootPackageName(ir *fernir.IntermediateRepresentation, packageNameOverri return strings.ToLower(ir.ApiName.CamelCase.SafeName) } +// generatorexecEndpointSnippetToString returns the string representation of the given +// endpoint snippet. +// +// It isn't enough to sort based on the endpoint path and method along (there can be duplicates), +// so we include the snippet's content to disambiguate. +func generatorexecEndpointSnippetToString(endpointSnippet *generatorexec.Endpoint) string { + if endpointSnippet == nil || endpointSnippet.Id == nil { + return "" + } + var goSnippet string + if endpointSnippet.Snippet != nil && endpointSnippet.Snippet.Go != nil { + goSnippet = endpointSnippet.Snippet.Go.Client + } + return fmt.Sprintf( + "%s.%s.%s", + endpointSnippet.Id.Path, + endpointSnippet.Id.Method, + goSnippet, + ) +} + // pointerFunctionNames enumerates all of the pointer function names. var pointerFunctionNames = map[string]struct{}{ "Bool": struct{}{}, diff --git a/generators/go/internal/generator/sdk.go b/generators/go/internal/generator/sdk.go index ade1f38bed1..eb1beb99c16 100644 --- a/generators/go/internal/generator/sdk.go +++ b/generators/go/internal/generator/sdk.go @@ -10,6 +10,7 @@ import ( "github.com/fern-api/fern-go/internal/ast" "github.com/fern-api/fern-go/internal/fern/ir" "github.com/fern-api/fern-go/internal/gospec" + generatorexec "github.com/fern-api/generator-exec-go" ) // goLanguageHeader is the identifier used for the X-Fern-Language platform header. @@ -662,12 +663,12 @@ func (f *fileWriter) WriteRequestOptions( ) if i == 0 { option = ast.NewCallExpr( - ast.NewImportedObject( + ast.NewImportedReference( optionName, importPath, ), []ast.Expr{ - ast.NewLocalObject(`""`), + ast.NewBasicLit(`""`), }, ) } @@ -687,13 +688,13 @@ func (f *fileWriter) WriteRequestOptions( if authScheme.Basic != nil { if i == 0 { option = ast.NewCallExpr( - ast.NewImportedObject( + ast.NewImportedReference( "WithBasicAuth", importPath, ), []ast.Expr{ - ast.NewLocalObject(`""`), - ast.NewLocalObject(`""`), + ast.NewBasicLit(`""`), + ast.NewBasicLit(`""`), }, ) } @@ -725,12 +726,12 @@ func (f *fileWriter) WriteRequestOptions( ) if i == 0 { option = ast.NewCallExpr( - ast.NewImportedObject( + ast.NewImportedReference( optionName, importPath, ), []ast.Expr{ - ast.NewLocalObject(fmt.Sprintf(`""`, pascalCase)), + ast.NewBasicLit(fmt.Sprintf(`""`, pascalCase)), }, ) } @@ -786,6 +787,12 @@ func (f *fileWriter) WriteRequestOptions( type GeneratedClient struct { Instantiation *ast.AssignStmt + Endpoints []*GeneratedEndpoint +} + +type GeneratedEndpoint struct { + Identifier *generatorexec.EndpointIdentifier + Snippet ast.Expr } // WriteClient writes a client for interacting with the given service. @@ -796,8 +803,7 @@ func (f *fileWriter) WriteClient( environmentsConfig *ir.EnvironmentsConfig, errorDiscriminationStrategy *ir.ErrorDiscriminationStrategy, fernFilepath *ir.FernFilepath, - generatedAuth *GeneratedAuth, - generatedEnvironment *GeneratedEnvironment, + rootClientInstantiation *ast.AssignStmt, ) (*GeneratedClient, error) { var ( clientName = "Client" @@ -1122,29 +1128,384 @@ func (f *fileWriter) WriteClient( f.P() } } + return NewGeneratedClient( + f, + fernFilepath, + irEndpoints, + rootClientInstantiation, + ) +} + +// NewGeneratedClient constructs the snippets associated with the client's +// endpoints. +func NewGeneratedClient( + f *fileWriter, + fernFilepath *ir.FernFilepath, + endpoints []*ir.HttpEndpoint, + rootClientInstantiation *ast.AssignStmt, +) (*GeneratedClient, error) { + var generatedEndpoints []*GeneratedEndpoint + for _, endpoint := range endpoints { + if len(endpoint.Examples) == 0 { + continue + } + generatedEndpoints = append( + generatedEndpoints, + newGeneratedEndpoint( + f, + fernFilepath, + rootClientInstantiation, + endpoint, + endpoint.Examples[0], // Generate a snippet for the first example. + ), + ) + } + return &GeneratedClient{ + Instantiation: rootClientInstantiation, + Endpoints: generatedEndpoints, + }, nil +} + +func newGeneratedEndpoint( + f *fileWriter, + fernFilepath *ir.FernFilepath, + rootClientInstantiation *ast.AssignStmt, + endpoint *ir.HttpEndpoint, + example *ir.ExampleEndpointCall, +) *GeneratedEndpoint { + return &GeneratedEndpoint{ + Identifier: endpointToIdentifier(endpoint), + Snippet: newEndpointSnippet( + f, + fernFilepath, + rootClientInstantiation, + endpoint, + example, + ), + } +} + +func endpointToIdentifier(endpoint *ir.HttpEndpoint) *generatorexec.EndpointIdentifier { + return &generatorexec.EndpointIdentifier{ + Path: fullPathForEndpoint(endpoint), + Method: irMethodToGeneratorExecMethod(endpoint.Method), + } +} + +func fullPathForEndpoint(endpoint *ir.HttpEndpoint) string { + var components []string + if head := strings.Trim(endpoint.FullPath.Head, "/"); len(head) > 0 { + components = append(components, head) + } + for _, part := range endpoint.FullPath.Parts { + components = append(components, fmt.Sprintf("{%s}", part.PathParameter)) + if tail := strings.Trim(part.Tail, "/"); len(tail) > 0 { + components = append(components, tail) + } + } + return fmt.Sprintf("/%s", strings.Join(components, "/")) +} + +func irMethodToGeneratorExecMethod(method ir.HttpMethod) generatorexec.EndpointMethod { + switch method { + case ir.HttpMethodGet: + return generatorexec.EndpointMethodGet + case ir.HttpMethodPost: + return generatorexec.EndpointMethodPost + case ir.HttpMethodPut: + return generatorexec.EndpointMethodPut + case ir.HttpMethodPatch: + return generatorexec.EndpointMethodPatch + case ir.HttpMethodDelete: + return generatorexec.EndpointMethodDelete + } + return 0 +} + +func newEndpointSnippet( + f *fileWriter, + fernFilepath *ir.FernFilepath, + rootClientInstantiation *ast.AssignStmt, + endpoint *ir.HttpEndpoint, + example *ir.ExampleEndpointCall, +) *ast.Block { + methodName := getEndpointMethodName(fernFilepath, endpoint) + parameters := getEndpointParameters( + f, + fernFilepath, + endpoint, + example, + ) + call := &ast.CallExpr{ + FunctionName: &ast.LocalReference{ + Name: fmt.Sprintf("client.%s", methodName), + }, + Parameters: parameters, + } + returnValues := []ast.Expr{ + &ast.LocalReference{ + Name: "err", + }, + } + if endpoint.Response != nil { + returnValues = append( + []ast.Expr{ + &ast.LocalReference{ + Name: "response", + }, + }, + returnValues..., + ) + } + endpointCall := &ast.AssignStmt{ + Left: returnValues, + Right: []ast.Expr{ + call, + }, + } + return &ast.Block{ + Exprs: []ast.Expr{ + rootClientInstantiation, + endpointCall, + }, + } +} + +func getEndpointMethodName( + fernFilepath *ir.FernFilepath, + endpoint *ir.HttpEndpoint, +) string { + var packageElements []string + for _, packageElem := range fernFilepath.PackagePath { + packageElements = append(packageElements, packageElem.PascalCase.UnsafeName) + } + if fernFilepath.File != nil { + packageElements = append(packageElements, fernFilepath.File.PascalCase.UnsafeName) + } + methodName := endpoint.Name.PascalCase.UnsafeName + if len(packageElements) == 0 { + return methodName + } + return fmt.Sprintf("%s.%s", strings.Join(packageElements, "."), methodName) +} + +func getEndpointParameters( + f *fileWriter, + fernFilepath *ir.FernFilepath, + endpoint *ir.HttpEndpoint, + example *ir.ExampleEndpointCall, +) []ast.Expr { + parameters := []ast.Expr{ + &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: "TODO", + ImportPath: "context", + }, + }, + } + + allPathParameters := example.RootPathParameters + allPathParameters = append(allPathParameters, example.ServicePathParameters...) + allPathParameters = append(allPathParameters, example.EndpointPathParameters...) + for _, pathParameter := range allPathParameters { + parameters = append( + parameters, + f.snippetWriter.GetSnippetForExampleTypeReference(pathParameter.Value), + ) + } + + var fields []*ast.Field + for _, header := range append(example.ServiceHeaders, example.EndpointHeaders...) { + if isHeaderLiteral(endpoint, header.Name.WireValue) { + continue + } + fields = append( + fields, + &ast.Field{ + Key: header.Name.Name.PascalCase.UnsafeName, + Value: f.snippetWriter.GetSnippetForExampleTypeReference(header.Value), + }, + ) + } + + for _, queryParameter := range example.QueryParameters { + if isQueryParameterLiteral(endpoint, queryParameter.Name.WireValue) { + continue + } + exampleValue := f.snippetWriter.GetSnippetForExampleTypeReference(queryParameter.Value) + if isQueryParameterAllowMultiple(endpoint, queryParameter.Name.WireValue) { + // This query parameter allows multiple elements, so we need to surround the example + // value in an array. + expr := exampleTypeReferenceShapeToGoType( + queryParameter.Value.Shape, + f.types, + f.baseImportPath, + ) + exampleValue = &ast.ArrayLit{ + Type: &ast.ArrayType{ + Expr: expr, + }, + Values: []ast.Expr{ + exampleValue, + }, + } + } + fields = append( + fields, + &ast.Field{ + Key: queryParameter.Name.Name.PascalCase.UnsafeName, + Value: exampleValue, + }, + ) + } + + if !shouldSkipRequestType(endpoint) { + fields = append( + fields, + exampleRequestBodyToFields(f, endpoint, example.Request)..., + ) + parameters = append( + parameters, + &ast.StructType{ + Name: &ast.ImportedReference{ + Name: endpoint.SdkRequest.Shape.Wrapper.WrapperName.PascalCase.UnsafeName, + ImportPath: fernFilepathToImportPath(f.baseImportPath, fernFilepath), + }, + Fields: fields, + }, + ) + return parameters + } + + if example.Request != nil && example.Request.Reference != nil { + // Include the body as an ordinary parameter alongside the rest. + parameters = append( + parameters, + f.snippetWriter.GetSnippetForExampleTypeReference(example.Request.Reference), + ) + } + return parameters +} + +func exampleRequestBodyToFields( + f *fileWriter, + endpoint *ir.HttpEndpoint, + exampleRequestBody *ir.ExampleRequestBody, +) []*ast.Field { + if exampleRequestBody == nil { + return nil + } + var fields []*ast.Field + switch exampleRequestBody.Type { + case "inlinedRequestBody": + for _, property := range exampleRequestBody.InlinedRequestBody.Properties { + if isExampleInlinedRequestBodyPropertyLiteral(endpoint, property) { + return fields + } + fields = append( + fields, + &ast.Field{ + Key: property.Name.Name.PascalCase.UnsafeName, + Value: f.snippetWriter.GetSnippetForExampleTypeReference(property.Value), + }, + ) + } + return fields + case "reference": + if isExampleReferenceRequestBodyLiteral(endpoint) { + return fields + } + fields = append( + fields, + &ast.Field{ + Key: endpoint.SdkRequest.Shape.Wrapper.BodyKey.PascalCase.UnsafeName, + Value: f.snippetWriter.GetSnippetForExampleTypeReference(exampleRequestBody.Reference), + }, + ) + } + return fields +} + +func isHeaderLiteral(endpoint *ir.HttpEndpoint, wireValue string) bool { + for _, header := range endpoint.Headers { + if header.Name.WireValue == wireValue { + return isTypeReferenceLiteral(header.ValueType) + } + } + return false +} + +func isQueryParameterLiteral(endpoint *ir.HttpEndpoint, wireValue string) bool { + for _, queryParameter := range endpoint.QueryParameters { + if queryParameter.Name.WireValue == wireValue { + return isTypeReferenceLiteral(queryParameter.ValueType) + } + } + return false +} + +func isQueryParameterAllowMultiple(endpoint *ir.HttpEndpoint, wireValue string) bool { + for _, queryParameter := range endpoint.QueryParameters { + if queryParameter.Name.WireValue == wireValue { + return queryParameter.AllowMultiple + } + } + return false +} + +func isExampleInlinedRequestBodyPropertyLiteral( + endpoint *ir.HttpEndpoint, + exampleInlinedRequestBodyProperty *ir.ExampleInlinedRequestBodyProperty, +) bool { + if endpoint.RequestBody == nil || endpoint.RequestBody.InlinedRequestBody == nil { + return false + } + wireValue := exampleInlinedRequestBodyProperty.Name.WireValue + for _, property := range endpoint.RequestBody.InlinedRequestBody.Properties { + if property.Name.WireValue == wireValue { + return isTypeReferenceLiteral(property.ValueType) + } + } + return false +} + +func isExampleReferenceRequestBodyLiteral( + endpoint *ir.HttpEndpoint, +) bool { + if endpoint.RequestBody == nil || endpoint.RequestBody.Reference == nil { + return false + } + return isTypeReferenceLiteral(endpoint.RequestBody.Reference.RequestBodyType) +} + +// generatedClientInstantiation returns the AST expression associated with +// the client construction (including import statements and client options). +func generatedClientInstantiation( + baseImportPath string, + generatedAuth *GeneratedAuth, + generatedEnvironment *GeneratedEnvironment, +) *ast.AssignStmt { var parameters []ast.Expr if generatedAuth != nil { parameters = append(parameters, generatedAuth.Option) } if generatedEnvironment != nil { - parameters = append(parameters, generatedEnvironment.Example) + parameters = append(parameters, generatedEnvironment.Option) } - return &GeneratedClient{ - Instantiation: &ast.AssignStmt{ - Left: []ast.Expr{ - ast.NewLocalObject("client"), - }, - Right: []ast.Expr{ - ast.NewCallExpr( - ast.NewImportedObject( - "NewClient", - packagePathToImportPath(f.baseImportPath, packagePathForClient(fernFilepath)), - ), - parameters, + return &ast.AssignStmt{ + Left: []ast.Expr{ + ast.NewLocalReference("client"), + }, + Right: []ast.Expr{ + ast.NewCallExpr( + ast.NewImportedReference( + "NewClient", + packagePathToImportPath(baseImportPath, packagePathForClient(new(ir.FernFilepath))), ), - }, + parameters, + ), }, - }, nil + } } // endpoint holds the fields required to generate a client endpoint. @@ -1485,6 +1846,7 @@ func (f *fileWriter) endpointFromIR( // GeneratedEnvironment contains information about the environments that were generated. type GeneratedEnvironment struct { Example ast.Expr // e.g. acme.Environments.Production + Option ast.Expr // e.g. option.WithBaseURL(acme.Environments.Production) } // WriteEnvironments writes the environment constants. @@ -1789,6 +2151,15 @@ func environmentsToEnvironmentsVariable( } return &GeneratedEnvironment{ Example: declarationVisitor.value, + Option: &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: "WithBaseURL", + ImportPath: path.Join(writer.baseImportPath, "option"), + }, + Parameters: []ast.Expr{ + declarationVisitor.value, + }, + }, }, nil } @@ -1824,7 +2195,7 @@ type environmentsDeclarationVisitor struct { func (e *environmentsDeclarationVisitor) VisitSingleBaseUrl(url *ir.SingleBaseUrlEnvironments) error { for i, environment := range url.Environments { if i == 0 { - e.value = ast.NewImportedObject( + e.value = ast.NewImportedReference( fmt.Sprintf("Environments.%s", environment.Name.PascalCase.UnsafeName), e.importPath, ) @@ -1842,7 +2213,7 @@ func (e *environmentsDeclarationVisitor) VisitMultipleBaseUrls(url *ir.MultipleB } for i, environment := range url.Environments { if i == 0 { - e.value = ast.NewImportedObject( + e.value = ast.NewImportedReference( fmt.Sprintf("Environments.%s", environment.Name.PascalCase.UnsafeName), e.importPath, ) @@ -2176,6 +2547,9 @@ func needsRequestParameter(endpoint *ir.HttpEndpoint) bool { if len(endpoint.QueryParameters) > 0 { return true } + if len(endpoint.Headers) > 0 { + return true + } if endpoint.RequestBody != nil { return endpoint.RequestBody.FileUpload == nil || fileUploadHasBodyProperties(endpoint.RequestBody.FileUpload) } diff --git a/generators/go/internal/generator/sdk/core/pointer.go b/generators/go/internal/generator/sdk/core/pointer.go index 1cdd4daa8cb..dc880408255 100644 --- a/generators/go/internal/generator/sdk/core/pointer.go +++ b/generators/go/internal/generator/sdk/core/pointer.go @@ -1,6 +1,10 @@ package core -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/generator/snippet.go b/generators/go/internal/generator/snippet.go new file mode 100644 index 00000000000..c199c81a603 --- /dev/null +++ b/generators/go/internal/generator/snippet.go @@ -0,0 +1,725 @@ +package generator + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/fern-api/fern-go/internal/ast" + "github.com/fern-api/fern-go/internal/fern/ir" +) + +// SnippetWriter writes codes snippets as AST expressions from examples. +type SnippetWriter struct { + baseImportPath string + types map[ir.TypeId]*ir.TypeDeclaration +} + +// NewSnippetWriter constructs a new *SnippetWriter. +func NewSnippetWriter( + baseImportPath string, + types map[ir.TypeId]*ir.TypeDeclaration, +) *SnippetWriter { + return &SnippetWriter{ + baseImportPath: baseImportPath, + types: types, + } +} + +// GetSnippetForExampleTypeReference returns an AST expression +// that represents the snippet for the given example. +func (s *SnippetWriter) GetSnippetForExampleTypeReference( + exampleTypeReference *ir.ExampleTypeReference, +) ast.Expr { + if exampleTypeReference == nil { + return nil + } + return s.getSnippetForExampleTypeReferenceShape(exampleTypeReference.Shape) +} + +func (s *SnippetWriter) getSnippetForExampleTypeReferenceShape( + exampleTypeShape *ir.ExampleTypeReferenceShape, +) ast.Expr { + if exampleTypeShape == nil { + return nil + } + switch exampleTypeShape.Type { + case "primitive": + return s.getSnippetForPrimitive(exampleTypeShape.Primitive) + case "container": + return s.getSnippetForContainer(exampleTypeShape.Container) + case "unknown": + return s.getSnippetForUnknown(exampleTypeShape.Unknown) + case "named": + return s.getSnippetForExampleTypeShape( + exampleTypeShape.Named.TypeName, + exampleTypeShape.Named.Shape, + ) + } + return nil +} + +func (s *SnippetWriter) getSnippetForExampleTypeShape( + declaredTypeName *ir.DeclaredTypeName, + exampleTypeShape *ir.ExampleTypeShape, +) ast.Expr { + importedReference := s.declaredTypeNameToImportedReference(declaredTypeName) + switch exampleTypeShape.Type { + case "alias": + return s.GetSnippetForExampleTypeReference(exampleTypeShape.Alias.Value) + case "enum": + return s.getSnippetForExampleEnumType( + importedReference, + exampleTypeShape.Enum, + ) + case "object": + return s.getSnippetForExampleObjectType( + importedReference, + exampleTypeShape.Object, + ) + case "union": + return s.getSnippetForExampleUnionType( + declaredTypeName, + importedReference, + exampleTypeShape.Union, + ) + case "undiscriminatedUnion": + return s.getSnippetForExampleUndiscriminatedUnionType( + declaredTypeName, + importedReference, + exampleTypeShape.UndiscriminatedUnion, + ) + } + return nil +} + +func (s *SnippetWriter) getSnippetForExampleEnumType( + importedReference *ast.ImportedReference, + exampleEnumType *ir.ExampleEnumType, +) ast.Expr { + return &ast.ImportedReference{ + Name: importedReference.Name + exampleEnumType.Value.Name.PascalCase.UnsafeName, + ImportPath: importedReference.ImportPath, + } +} + +func (s *SnippetWriter) getSnippetForExampleObjectType( + importedReference *ast.ImportedReference, + exampleObjectType *ir.ExampleObjectType, +) ast.Expr { + fields := make([]*ast.Field, 0, len(exampleObjectType.Properties)) + for _, property := range exampleObjectType.Properties { + if s.isExampleObjectPropertyLiteral(property) { + // Literal object properties aren't included in the snippet. + continue + } + fields = append( + fields, + &ast.Field{ + Key: property.Name.Name.PascalCase.UnsafeName, + Value: s.GetSnippetForExampleTypeReference(property.Value), + }, + ) + } + return &ast.StructType{ + Name: importedReference, + Fields: fields, + } +} + +func (s *SnippetWriter) getSnippetForExampleUnionType( + declaredTypeName *ir.DeclaredTypeName, + importedReference *ast.ImportedReference, + exampleUnionType *ir.ExampleUnionType, +) ast.Expr { + if s.isExampleUnionTypeLiteral(declaredTypeName, exampleUnionType) { + // Union literal constructors don't require any arguments. + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: fmt.Sprintf( + "New%sWith%s", + importedReference.Name, + exampleUnionType.SingleUnionType.WireDiscriminantValue.Name.PascalCase.UnsafeName, + ), + ImportPath: importedReference.ImportPath, + }, + } + } + var parameters []ast.Expr + if parameter := s.getSnippetForExampleSingleUnionTypeProperties(exampleUnionType.SingleUnionType.Shape); parameter != nil { + parameters = append(parameters, parameter) + } + if len(parameters) == 0 { + // This union type doesn't have any properties, so we can just construct it + // as an in-line struct. + return &ast.StructType{ + Name: importedReference, + Fields: []*ast.Field{ + { + Key: exampleUnionType.Discriminant.Name.PascalCase.UnsafeName, + Value: &ast.BasicLit{ + Value: fmt.Sprintf("%q", exampleUnionType.SingleUnionType.WireDiscriminantValue.Name.OriginalName), + }, + }, + }, + } + } + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: fmt.Sprintf( + "New%sFrom%s", + importedReference.Name, + exampleUnionType.SingleUnionType.WireDiscriminantValue.Name.PascalCase.UnsafeName, + ), + ImportPath: importedReference.ImportPath, + }, + Parameters: parameters, + } +} + +func (s *SnippetWriter) getSnippetForExampleSingleUnionTypeProperties( + exampleSingleUnionTypeProperties *ir.ExampleSingleUnionTypeProperties, +) ast.Expr { + switch exampleSingleUnionTypeProperties.Type { + case "samePropertiesAsObject": + typeDeclaration := s.types[exampleSingleUnionTypeProperties.SamePropertiesAsObject.TypeId] + if typeDeclaration == nil { + // This should be unreachable. + return nil + } + importedReference := s.declaredTypeNameToImportedReference(typeDeclaration.Name) + return s.getSnippetForExampleObjectType( + importedReference, + exampleSingleUnionTypeProperties.SamePropertiesAsObject.Object, + ) + case "singleProperty": + return s.GetSnippetForExampleTypeReference(exampleSingleUnionTypeProperties.SingleProperty) + } + return nil +} + +func (s *SnippetWriter) getSnippetForExampleUndiscriminatedUnionType( + declaredTypeName *ir.DeclaredTypeName, + importedReference *ast.ImportedReference, + exampleUndiscriminatedUnionType *ir.ExampleUndiscriminatedUnionType, +) ast.Expr { + member := s.getUndiscriminatedUnionMember(declaredTypeName, exampleUndiscriminatedUnionType) + if member == nil { + return nil + } + field := typeReferenceToUndiscriminatedUnionField(member.Type, s.types) + if isTypeReferenceLiteral(member.Type) { + // Union literal constructors don't require any arguments. + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: fmt.Sprintf( + "New%sWith%s", + importedReference.Name, + strings.Title(field), + ), + ImportPath: importedReference.ImportPath, + }, + } + } + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: fmt.Sprintf( + "New%sFrom%s", + importedReference.Name, + field, + ), + ImportPath: importedReference.ImportPath, + }, + Parameters: []ast.Expr{ + s.GetSnippetForExampleTypeReference(exampleUndiscriminatedUnionType.SingleUnionType), + }, + } +} + +func (s *SnippetWriter) getSnippetForContainer( + exampleContainer *ir.ExampleContainer, +) ast.Expr { + switch exampleContainer.Type { + case "list": + return s.getSnippetForListOrSet(exampleContainer.List) + case "set": + return s.getSnippetForListOrSet(exampleContainer.List) + case "optional": + if exampleContainer.Optional == nil { + return nil + } + if primitive := exampleContainer.Optional.Shape.Primitive; primitive != nil { + return s.getSnippetForOptionalPrimitive(primitive) + } + return s.GetSnippetForExampleTypeReference(exampleContainer.Optional) + case "map": + return s.getSnippetForMap(exampleContainer.Map) + } + return nil +} + +func (s *SnippetWriter) getSnippetForListOrSet( + exampleTypeReferences []*ir.ExampleTypeReference, +) ast.Expr { + index := -1 + var expressions []ast.Expr + for i, exampleTypeReference := range exampleTypeReferences { + expr := s.GetSnippetForExampleTypeReference(exampleTypeReference) + if expr == nil { + continue + } + if index == -1 { + index = i + } + expressions = append(expressions, expr) + } + if len(expressions) == 0 { + return nil + } + return &ast.ArrayLit{ + Type: &ast.ArrayType{ + Expr: exampleTypeReferenceShapeToGoType( + exampleTypeReferences[index].Shape, + s.types, + s.baseImportPath, + ), + }, + Values: expressions, + } +} + +func (s *SnippetWriter) getSnippetForMap( + exampleKeyValuePairs []*ir.ExampleKeyValuePair, +) ast.Expr { + var ( + keys []ast.Expr + values []ast.Expr + ) + for _, pair := range exampleKeyValuePairs { + key := s.GetSnippetForExampleTypeReference(pair.Key) + if key == nil { + continue + } + value := s.GetSnippetForExampleTypeReference(pair.Value) + if value == nil { + continue + } + keys = append(keys, key) + values = append(values, value) + } + if len(keys) == 0 { + return nil + } + + visitor := &exampleContainerVisitor{ + baseImportPath: s.baseImportPath, + types: s.types, + } + _ = visitor.VisitMap(exampleKeyValuePairs) + mapType, ok := visitor.value.(*ast.MapType) + if !ok { + // This should be unreachable. + return nil + } + + return &ast.MapLit{ + Type: mapType, + Keys: keys, + Values: values, + } +} + +func (s *SnippetWriter) getSnippetForUnknown( + unknownExample interface{}, +) ast.Expr { + // Serialize the unknown as a JSON object, and simply + // specify the object literal in-line. + bytes, err := json.Marshal(unknownExample) + if err != nil { + // If we fail to serialize as JSON, we can still use 'nil' + // as a valid example. + return &ast.BasicLit{ + Value: "nil", + } + } + return &ast.BasicLit{ + Value: fmt.Sprintf("%q", string(bytes)), + } +} + +func (s *SnippetWriter) getSnippetForOptionalPrimitive( + examplePrimitive *ir.ExamplePrimitive, +) ast.Expr { + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: examplePrimitiveToPointerConstructorName(examplePrimitive), + ImportPath: s.baseImportPath, + }, + Parameters: []ast.Expr{ + s.getSnippetForPrimitive(examplePrimitive), + }, + } +} + +func (s *SnippetWriter) getSnippetForPrimitive( + exampleTypeReference *ir.ExamplePrimitive, +) ast.Expr { + switch exampleTypeReference.Type { + case "integer": + return &ast.BasicLit{ + Value: strconv.Itoa(exampleTypeReference.Integer), + } + case "double": + return &ast.BasicLit{ + Value: strconv.FormatFloat(exampleTypeReference.Double, 'f', -1, 64), + } + case "string": + value := fmt.Sprintf("%q", exampleTypeReference.String.Original) + if strings.Contains(exampleTypeReference.String.Original, `"`) { + value = fmt.Sprintf("`%s`", exampleTypeReference.String.Original) + } + return &ast.BasicLit{ + Value: value, + } + case "boolean": + return &ast.BasicLit{ + Value: strconv.FormatBool(exampleTypeReference.Boolean), + } + case "long": + return &ast.BasicLit{ + Value: strconv.FormatInt(exampleTypeReference.Long, 64), + } + case "datetime": + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: "MustParseDateTime", + ImportPath: s.baseImportPath, + }, + Parameters: []ast.Expr{ + &ast.BasicLit{ + Value: fmt.Sprintf("%q", exampleTypeReference.Datetime.Format(time.RFC3339)), + }, + }, + } + case "date": + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: "MustParseDate", + ImportPath: s.baseImportPath, + }, + Parameters: []ast.Expr{ + &ast.BasicLit{ + Value: fmt.Sprintf("%q", exampleTypeReference.Date.Format("2006-01-02")), + }, + }, + } + case "uuid": + return &ast.CallExpr{ + FunctionName: &ast.ImportedReference{ + Name: "MustParse", + ImportPath: "github.com/google/uuid", + }, + Parameters: []ast.Expr{ + &ast.BasicLit{ + Value: fmt.Sprintf("%q", exampleTypeReference.Uuid.String()), + }, + }, + } + } + return nil +} + +func (s *SnippetWriter) isExampleUnionTypeLiteral( + declaredTypeName *ir.DeclaredTypeName, + exampleUnionType *ir.ExampleUnionType, +) bool { + typeDeclaration := s.types[declaredTypeName.TypeId] + if typeDeclaration == nil { + // This should be unreachable. + return false + } + switch typeDeclaration.Shape.Type { + case "union": + wireValue := exampleUnionType.SingleUnionType.WireDiscriminantValue.WireValue + for _, singleUnionType := range typeDeclaration.Shape.Union.Types { + if singleUnionType.DiscriminantValue.WireValue == wireValue { + return singleUnionType.Shape.SingleProperty != nil && isTypeReferenceLiteral(singleUnionType.Shape.SingleProperty.Type) + } + } + } + return false +} + +func (s *SnippetWriter) isExampleObjectPropertyLiteral( + exampleObjectProperty *ir.ExampleObjectProperty, +) bool { + typeDeclaration := s.types[exampleObjectProperty.OriginalTypeDeclaration.TypeId] + if typeDeclaration == nil { + // This should be unreachable. + return false + } + switch typeDeclaration.Shape.Type { + case "alias": + return isTypeReferenceLiteral(typeDeclaration.Shape.Alias.AliasOf) + case "object": + wireValue := exampleObjectProperty.Name.WireValue + for _, property := range typeDeclaration.Shape.Object.Properties { + if property.Name.WireValue == wireValue { + return isTypeReferenceLiteral(property.ValueType) + } + } + } + return false +} + +func (s *SnippetWriter) getUndiscriminatedUnionMember( + declaredTypeName *ir.DeclaredTypeName, + exampleUndiscriminatedUnionType *ir.ExampleUndiscriminatedUnionType, +) *ir.UndiscriminatedUnionMember { + undiscriminatedUnionType := s.getUndiscriminatedUnionTypeFromTypeName(declaredTypeName) + if undiscriminatedUnionType == nil { + // This should be unreachable. + return nil + } + return undiscriminatedUnionType.Members[exampleUndiscriminatedUnionType.Index] +} + +func (s *SnippetWriter) getUndiscriminatedUnionTypeFromTypeName( + declaredTypeName *ir.DeclaredTypeName, +) *ir.UndiscriminatedUnionTypeDeclaration { + typeDeclaration := s.types[declaredTypeName.TypeId] + if typeDeclaration == nil { + // This should be unreachable. + return nil + } + switch typeDeclaration.Shape.Type { + case "undiscriminatedUnion": + return typeDeclaration.Shape.UndiscriminatedUnion + } + return nil +} + +func (s *SnippetWriter) declaredTypeNameToImportedReference( + declaredTypeName *ir.DeclaredTypeName, +) *ast.ImportedReference { + return &ast.ImportedReference{ + ImportPath: fernFilepathToImportPath(s.baseImportPath, declaredTypeName.FernFilepath), + Name: declaredTypeName.Name.PascalCase.UnsafeName, + } +} + +func examplePrimitiveToPointerConstructorName( + examplePrimitive *ir.ExamplePrimitive, +) string { + switch examplePrimitive.Type { + case "integer": + return "Int" + case "double": + return "Float64" + case "string": + return "String" + case "boolean": + return "Bool" + case "long": + return "Int64" + case "datetime": + return "Time" + case "date": + return "Time" + case "uuid": + return "UUID" + } + return "Unknown" +} + +func isTypeReferenceLiteral(typeReference *ir.TypeReference) bool { + if typeReference.Container != nil { + if typeReference.Container.Optional != nil { + return isTypeReferenceLiteral(typeReference.Container.Optional) + } + return typeReference.Container.Literal != nil + } + return false +} + +// typeReferenceVisitor retrieves the string representation of type references +// (e.g. containers, primitives, etc). +type exampleTypeReferenceShapeVisitor struct { + value ast.Expr + + baseImportPath string + types map[ir.TypeId]*ir.TypeDeclaration +} + +// exampleTypeReferenceToGoType maps the given type reference into its Go-equivalent. +func exampleTypeReferenceShapeToGoType( + exampleTypeReferenceShape *ir.ExampleTypeReferenceShape, + types map[ir.TypeId]*ir.TypeDeclaration, + baseImportPath string, +) ast.Expr { + visitor := &exampleTypeReferenceShapeVisitor{ + baseImportPath: baseImportPath, + types: types, + } + _ = exampleTypeReferenceShape.Accept(visitor) + return visitor.value +} + +// Compile-time assertion. +var _ ir.ExampleTypeReferenceShapeVisitor = (*exampleTypeReferenceShapeVisitor)(nil) + +func (e *exampleTypeReferenceShapeVisitor) VisitContainer(container *ir.ExampleContainer) error { + e.value = exampleContainerTypeToGoType( + container, + e.types, + e.baseImportPath, + ) + return nil +} + +func (e *exampleTypeReferenceShapeVisitor) VisitNamed(named *ir.ExampleNamedType) error { + e.value = &ast.ImportedReference{ + Name: named.TypeName.Name.PascalCase.UnsafeName, + ImportPath: fernFilepathToImportPath(e.baseImportPath, named.TypeName.FernFilepath), + } + if isPointer(e.types[named.TypeName.TypeId]) { + e.value = &ast.Optional{ + Expr: e.value, + } + } + return nil +} + +func (e *exampleTypeReferenceShapeVisitor) VisitPrimitive(primitive *ir.ExamplePrimitive) error { + var primitiveType ir.PrimitiveType + switch primitive.Type { + case "integer": + primitiveType = ir.PrimitiveTypeInteger + case "double": + primitiveType = ir.PrimitiveTypeDouble + case "string": + primitiveType = ir.PrimitiveTypeString + case "boolean": + primitiveType = ir.PrimitiveTypeBoolean + case "long": + primitiveType = ir.PrimitiveTypeLong + case "datetime": + primitiveType = ir.PrimitiveTypeDateTime + case "date": + primitiveType = ir.PrimitiveTypeDate + case "uuid": + primitiveType = ir.PrimitiveTypeUuid + } + e.value = &ast.BasicLit{ + Value: primitiveToGoType(primitiveType), + } + return nil +} + +func (e *exampleTypeReferenceShapeVisitor) VisitUnknown(unknown any) error { + e.value = &ast.BasicLit{ + Value: unknownToGoType(unknown), + } + return nil +} + +type exampleContainerVisitor struct { + value ast.Expr + + baseImportPath string + types map[ir.TypeId]*ir.TypeDeclaration +} + +func exampleContainerTypeToGoType( + exampleContainer *ir.ExampleContainer, + types map[ir.TypeId]*ir.TypeDeclaration, + baseImportPath string, +) ast.Expr { + visitor := &exampleContainerVisitor{ + baseImportPath: baseImportPath, + types: types, + } + _ = exampleContainer.Accept(visitor) + return visitor.value +} + +// Compile-time assertion. +var _ ir.ExampleContainerVisitor = (*exampleContainerVisitor)(nil) + +func (e *exampleContainerVisitor) VisitList(list []*ir.ExampleTypeReference) error { + if len(list) == 0 { + // The example doesn't have enough information on its own. + e.value = &ast.ArrayType{ + Expr: &ast.LocalReference{ + Name: "interface{}", + }, + } + return nil + } + e.value = exampleTypeReferenceShapeToGoType( + list[0].Shape, + e.types, + e.baseImportPath, + ) + return nil +} + +func (e *exampleContainerVisitor) VisitSet(set []*ir.ExampleTypeReference) error { + if len(set) == 0 { + // The example doesn't have enough information on its own. + e.value = &ast.ArrayType{ + Expr: &ast.LocalReference{ + Name: "interface{}", + }, + } + return nil + } + e.value = exampleTypeReferenceShapeToGoType( + set[0].Shape, + e.types, + e.baseImportPath, + ) + return nil +} + +func (e *exampleContainerVisitor) VisitMap(pairs []*ir.ExampleKeyValuePair) error { + if len(pairs) == 0 { + // The example doesn't have enough information on its own. + e.value = &ast.MapType{ + Key: &ast.LocalReference{ + Name: "string", + }, + Value: &ast.LocalReference{ + Name: "interface{}", + }, + } + return nil + } + pair := pairs[0] + e.value = &ast.MapType{ + Key: exampleTypeReferenceShapeToGoType( + pair.Key.Shape, + e.types, + e.baseImportPath, + ), + Value: exampleTypeReferenceShapeToGoType( + pair.Value.Shape, + e.types, + e.baseImportPath, + ), + } + return nil +} + +func (e *exampleContainerVisitor) VisitOptional(optional *ir.ExampleTypeReference) error { + e.value = &ast.Optional{ + Expr: exampleTypeReferenceShapeToGoType( + optional.Shape, + e.types, + e.baseImportPath, + ), + } + return nil +} diff --git a/generators/go/internal/testdata/sdk/auth/fixtures/pointer.go b/generators/go/internal/testdata/sdk/auth/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/auth/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/auth/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/basic/fixtures/pointer.go b/generators/go/internal/testdata/sdk/basic/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/basic/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/basic/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/bearer-token-name/fixtures/pointer.go b/generators/go/internal/testdata/sdk/bearer-token-name/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/bearer-token-name/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/bearer-token-name/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/bearer/fixtures/pointer.go b/generators/go/internal/testdata/sdk/bearer/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/bearer/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/bearer/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/bytes/fixtures/pointer.go b/generators/go/internal/testdata/sdk/bytes/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/bytes/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/bytes/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/client-options-core/fixtures/pointer.go b/generators/go/internal/testdata/sdk/client-options-core/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/client-options-core/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/client-options-core/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/client-options-filename/fixtures/pointer.go b/generators/go/internal/testdata/sdk/client-options-filename/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/client-options-filename/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/client-options-filename/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/cycle/fixtures/pointer.go b/generators/go/internal/testdata/sdk/cycle/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/cycle/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/cycle/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/default/fixtures/pointer.go b/generators/go/internal/testdata/sdk/default/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/default/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/default/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/docs/fixtures/pointer.go b/generators/go/internal/testdata/sdk/docs/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/docs/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/docs/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/download/fixtures/pointer.go b/generators/go/internal/testdata/sdk/download/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/download/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/download/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/empty/fixtures/pointer.go b/generators/go/internal/testdata/sdk/empty/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/empty/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/empty/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/environments-core/fixtures/pointer.go b/generators/go/internal/testdata/sdk/environments-core/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/environments-core/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/environments-core/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/environments/fixtures/pointer.go b/generators/go/internal/testdata/sdk/environments/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/environments/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/environments/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/error-discrimination/fixtures/pointer.go b/generators/go/internal/testdata/sdk/error-discrimination/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/error-discrimination/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/error-discrimination/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/error/fixtures/pointer.go b/generators/go/internal/testdata/sdk/error/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/error/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/error/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/headers/fixtures/pointer.go b/generators/go/internal/testdata/sdk/headers/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/headers/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/headers/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/mergent/fixtures/pointer.go b/generators/go/internal/testdata/sdk/mergent/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/mergent/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/mergent/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/multi-environments/fixtures/pointer.go b/generators/go/internal/testdata/sdk/multi-environments/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/multi-environments/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/multi-environments/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/optional-core/fixtures/pointer.go b/generators/go/internal/testdata/sdk/optional-core/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/optional-core/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/optional-core/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/optional-filename/fixtures/pointer.go b/generators/go/internal/testdata/sdk/optional-filename/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/optional-filename/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/optional-filename/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/optional-response/fixtures/pointer.go b/generators/go/internal/testdata/sdk/optional-response/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/optional-response/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/optional-response/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/packages/fixtures/pointer.go b/generators/go/internal/testdata/sdk/packages/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/packages/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/packages/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/path-and-query-params/fixtures/pointer.go b/generators/go/internal/testdata/sdk/path-and-query-params/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/path-and-query-params/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/path-and-query-params/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/path-params/fixtures/pointer.go b/generators/go/internal/testdata/sdk/path-params/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/path-params/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/path-params/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/platform-headers/fixtures/pointer.go b/generators/go/internal/testdata/sdk/platform-headers/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/platform-headers/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/platform-headers/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/pointer-core/fixtures/core/pointer.go b/generators/go/internal/testdata/sdk/pointer-core/fixtures/core/pointer.go index 1cdd4daa8cb..dc880408255 100644 --- a/generators/go/internal/testdata/sdk/pointer-core/fixtures/core/pointer.go +++ b/generators/go/internal/testdata/sdk/pointer-core/fixtures/core/pointer.go @@ -1,6 +1,10 @@ package core -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/pointer-filename/fixtures/_pointer.go b/generators/go/internal/testdata/sdk/pointer-filename/fixtures/_pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/pointer-filename/fixtures/_pointer.go +++ b/generators/go/internal/testdata/sdk/pointer-filename/fixtures/_pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/pointer.go b/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/post-with-path-params-generics/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/pointer.go b/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/post-with-path-params/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/query-params-complex/fixtures/pointer.go b/generators/go/internal/testdata/sdk/query-params-complex/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/query-params-complex/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/query-params-complex/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/query-params-multiple/fixtures/pointer.go b/generators/go/internal/testdata/sdk/query-params-multiple/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/query-params-multiple/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/query-params-multiple/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/query-params/fixtures/pointer.go b/generators/go/internal/testdata/sdk/query-params/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/query-params/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/query-params/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/root/fixtures/pointer.go b/generators/go/internal/testdata/sdk/root/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/root/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/root/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/internal/testdata/sdk/upload/fixtures/pointer.go b/generators/go/internal/testdata/sdk/upload/fixtures/pointer.go index 82fb917b1f0..faaf462e6d0 100644 --- a/generators/go/internal/testdata/sdk/upload/fixtures/pointer.go +++ b/generators/go/internal/testdata/sdk/upload/fixtures/pointer.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/sdk/CHANGELOG.md b/generators/go/sdk/CHANGELOG.md index 880922e0190..b97f358c952 100644 --- a/generators/go/sdk/CHANGELOG.md +++ b/generators/go/sdk/CHANGELOG.md @@ -7,11 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## Unreleased + +- Fix: Package documentation is now generated into the correct package's `doc.go`. +- Feature: Add support for generated endpoint snippets. + - The snippets will be available in the API reference documentation, as well as the + snippets API. + - The snippets are _not_ embedded in the SDK itself (yet). + +```go +import ( + context "context" + time "time" + acme "github.com/acme/acme-go" + acmeclient "github.com/acme/acme-go/client" + option "github.com/acme/acme-go/option" +) + +client := acmeclient.NewClient( + option.WithToken( + "", + ), +) +response, err := client.User.Create( + context.TODO(), + &acme.CreateUserRequest{ + Username: "john", + Language: acme.LanguageEnglish, + Age: acme.Int(32), + Birthday: acme.MustParseDate( + "1980-06-01" + ), + }, +) +``` + ## [0.16.0] - 2024-02-12 -- Feature: The generator now supports whitelabelling. When this is turned on, - there will be no mention of Fern in the generated code. - **Note**: You must be on the enterprise tier to enable this mode. +- Feature: The generator now supports whitelabelling. When this is turned on, + there will be no mention of Fern in the generated code. + + **Note**: You must be on the enterprise tier to enable this mode. ## [0.15.0] - 2024-02-09 diff --git a/generators/go/seed/sdk/file-upload/service.go b/generators/go/seed/sdk/file-upload/service.go deleted file mode 100644 index e5b03ce7800..00000000000 --- a/generators/go/seed/sdk/file-upload/service.go +++ /dev/null @@ -1,287 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -package fileupload - -import ( - json "encoding/json" - fmt "fmt" - core "github.com/file-upload/fern/core" -) - -type JustFileWithQueryParamsRequet struct { - MaybeString *string `json:"-" url:"maybeString,omitempty"` - Integer int `json:"-" url:"integer"` - MaybeInteger *int `json:"-" url:"maybeInteger,omitempty"` - ListOfStrings []string `json:"-" url:"listOfStrings"` - OptionalListOfStrings []*string `json:"-" url:"optionalListOfStrings,omitempty"` -} - -type MyRequest struct { - MaybeString *string `json:"maybeString,omitempty" url:"maybeString,omitempty"` - Integer int `json:"integer" url:"integer"` - MaybeInteger *int `json:"maybeInteger,omitempty" url:"maybeInteger,omitempty"` - ListOfStrings []string `json:"listOfStrings,omitempty" url:"listOfStrings,omitempty"` - SetOfStrings []string `json:"setOfStrings,omitempty" url:"setOfStrings,omitempty"` - OptionalListOfStrings []string `json:"optionalListOfStrings,omitempty" url:"optionalListOfStrings,omitempty"` - OptionalSetOfStrings []string `json:"optionalSetOfStrings,omitempty" url:"optionalSetOfStrings,omitempty"` - MaybeList *MaybeList `json:"maybeList,omitempty" url:"maybeList,omitempty"` - OptionalMaybeList *MaybeList `json:"optionalMaybeList,omitempty" url:"optionalMaybeList,omitempty"` - MaybeListOrSet *MaybeListOrSet `json:"maybeListOrSet,omitempty" url:"maybeListOrSet,omitempty"` - OptionalMaybeListOrSet *MaybeListOrSet `json:"optionalMaybeListOrSet,omitempty" url:"optionalMaybeListOrSet,omitempty"` - ListOfObjects []*MyObject `json:"listOfObjects,omitempty" url:"listOfObjects,omitempty"` -} - -type MaybeList struct { - typeName string - String string - StringList []string - Integer int - IntegerList []int - IntegerListList [][]int -} - -func NewMaybeListFromString(value string) *MaybeList { - return &MaybeList{typeName: "string", String: value} -} - -func NewMaybeListFromStringList(value []string) *MaybeList { - return &MaybeList{typeName: "stringList", StringList: value} -} - -func NewMaybeListFromInteger(value int) *MaybeList { - return &MaybeList{typeName: "integer", Integer: value} -} - -func NewMaybeListFromIntegerList(value []int) *MaybeList { - return &MaybeList{typeName: "integerList", IntegerList: value} -} - -func NewMaybeListFromIntegerListList(value [][]int) *MaybeList { - return &MaybeList{typeName: "integerListList", IntegerListList: value} -} - -func (m *MaybeList) UnmarshalJSON(data []byte) error { - var valueString string - if err := json.Unmarshal(data, &valueString); err == nil { - m.typeName = "string" - m.String = valueString - return nil - } - var valueStringList []string - if err := json.Unmarshal(data, &valueStringList); err == nil { - m.typeName = "stringList" - m.StringList = valueStringList - return nil - } - var valueInteger int - if err := json.Unmarshal(data, &valueInteger); err == nil { - m.typeName = "integer" - m.Integer = valueInteger - return nil - } - var valueIntegerList []int - if err := json.Unmarshal(data, &valueIntegerList); err == nil { - m.typeName = "integerList" - m.IntegerList = valueIntegerList - return nil - } - var valueIntegerListList [][]int - if err := json.Unmarshal(data, &valueIntegerListList); err == nil { - m.typeName = "integerListList" - m.IntegerListList = valueIntegerListList - return nil - } - return fmt.Errorf("%s cannot be deserialized as a %T", data, m) -} - -func (m MaybeList) MarshalJSON() ([]byte, error) { - switch m.typeName { - default: - return nil, fmt.Errorf("invalid type %s in %T", m.typeName, m) - case "string": - return json.Marshal(m.String) - case "stringList": - return json.Marshal(m.StringList) - case "integer": - return json.Marshal(m.Integer) - case "integerList": - return json.Marshal(m.IntegerList) - case "integerListList": - return json.Marshal(m.IntegerListList) - } -} - -type MaybeListVisitor interface { - VisitString(string) error - VisitStringList([]string) error - VisitInteger(int) error - VisitIntegerList([]int) error - VisitIntegerListList([][]int) error -} - -func (m *MaybeList) Accept(visitor MaybeListVisitor) error { - switch m.typeName { - default: - return fmt.Errorf("invalid type %s in %T", m.typeName, m) - case "string": - return visitor.VisitString(m.String) - case "stringList": - return visitor.VisitStringList(m.StringList) - case "integer": - return visitor.VisitInteger(m.Integer) - case "integerList": - return visitor.VisitIntegerList(m.IntegerList) - case "integerListList": - return visitor.VisitIntegerListList(m.IntegerListList) - } -} - -type MaybeListOrSet struct { - typeName string - String string - StringList []string - Integer int - IntegerList []int - IntegerListList [][]int - StringSet []string -} - -func NewMaybeListOrSetFromString(value string) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "string", String: value} -} - -func NewMaybeListOrSetFromStringList(value []string) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "stringList", StringList: value} -} - -func NewMaybeListOrSetFromInteger(value int) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "integer", Integer: value} -} - -func NewMaybeListOrSetFromIntegerList(value []int) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "integerList", IntegerList: value} -} - -func NewMaybeListOrSetFromIntegerListList(value [][]int) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "integerListList", IntegerListList: value} -} - -func NewMaybeListOrSetFromStringSet(value []string) *MaybeListOrSet { - return &MaybeListOrSet{typeName: "stringSet", StringSet: value} -} - -func (m *MaybeListOrSet) UnmarshalJSON(data []byte) error { - var valueString string - if err := json.Unmarshal(data, &valueString); err == nil { - m.typeName = "string" - m.String = valueString - return nil - } - var valueStringList []string - if err := json.Unmarshal(data, &valueStringList); err == nil { - m.typeName = "stringList" - m.StringList = valueStringList - return nil - } - var valueInteger int - if err := json.Unmarshal(data, &valueInteger); err == nil { - m.typeName = "integer" - m.Integer = valueInteger - return nil - } - var valueIntegerList []int - if err := json.Unmarshal(data, &valueIntegerList); err == nil { - m.typeName = "integerList" - m.IntegerList = valueIntegerList - return nil - } - var valueIntegerListList [][]int - if err := json.Unmarshal(data, &valueIntegerListList); err == nil { - m.typeName = "integerListList" - m.IntegerListList = valueIntegerListList - return nil - } - var valueStringSet []string - if err := json.Unmarshal(data, &valueStringSet); err == nil { - m.typeName = "stringSet" - m.StringSet = valueStringSet - return nil - } - return fmt.Errorf("%s cannot be deserialized as a %T", data, m) -} - -func (m MaybeListOrSet) MarshalJSON() ([]byte, error) { - switch m.typeName { - default: - return nil, fmt.Errorf("invalid type %s in %T", m.typeName, m) - case "string": - return json.Marshal(m.String) - case "stringList": - return json.Marshal(m.StringList) - case "integer": - return json.Marshal(m.Integer) - case "integerList": - return json.Marshal(m.IntegerList) - case "integerListList": - return json.Marshal(m.IntegerListList) - case "stringSet": - return json.Marshal(m.StringSet) - } -} - -type MaybeListOrSetVisitor interface { - VisitString(string) error - VisitStringList([]string) error - VisitInteger(int) error - VisitIntegerList([]int) error - VisitIntegerListList([][]int) error - VisitStringSet([]string) error -} - -func (m *MaybeListOrSet) Accept(visitor MaybeListOrSetVisitor) error { - switch m.typeName { - default: - return fmt.Errorf("invalid type %s in %T", m.typeName, m) - case "string": - return visitor.VisitString(m.String) - case "stringList": - return visitor.VisitStringList(m.StringList) - case "integer": - return visitor.VisitInteger(m.Integer) - case "integerList": - return visitor.VisitIntegerList(m.IntegerList) - case "integerListList": - return visitor.VisitIntegerListList(m.IntegerListList) - case "stringSet": - return visitor.VisitStringSet(m.StringSet) - } -} - -type MyObject struct { - Foo string `json:"foo" url:"foo"` - - _rawJSON json.RawMessage -} - -func (m *MyObject) UnmarshalJSON(data []byte) error { - type unmarshaler MyObject - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { - return err - } - *m = MyObject(value) - m._rawJSON = json.RawMessage(data) - return nil -} - -func (m *MyObject) String() string { - if len(m._rawJSON) > 0 { - if value, err := core.StringifyJSON(m._rawJSON); err == nil { - return value - } - } - if value, err := core.StringifyJSON(m); err == nil { - return value - } - return fmt.Sprintf("%#v", m) -} diff --git a/generators/go/seed/sdk/literal/literal.go b/generators/go/seed/sdk/literal/literal.go deleted file mode 100644 index b5b056c7478..00000000000 --- a/generators/go/seed/sdk/literal/literal.go +++ /dev/null @@ -1,299 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -package literal - -import ( - json "encoding/json" - fmt "fmt" - core "github.com/literal/fern/core" -) - -type CreateOptionsRequest struct { - Values map[string]string `json:"values,omitempty" url:"values,omitempty"` -} - -type GetOptionsRequest struct { - dryRun bool -} - -func (g *GetOptionsRequest) DryRun() bool { - return g.dryRun -} - -func (g *GetOptionsRequest) UnmarshalJSON(data []byte) error { - type unmarshaler GetOptionsRequest - var body unmarshaler - if err := json.Unmarshal(data, &body); err != nil { - return err - } - *g = GetOptionsRequest(body) - g.dryRun = true - return nil -} - -func (g *GetOptionsRequest) MarshalJSON() ([]byte, error) { - type embed GetOptionsRequest - var marshaler = struct { - embed - DryRun bool `json:"dryRun"` - }{ - embed: embed(*g), - DryRun: true, - } - return json.Marshal(marshaler) -} - -type GetUndiscriminatedOptionsRequest struct { - dryRun bool -} - -func (g *GetUndiscriminatedOptionsRequest) DryRun() bool { - return g.dryRun -} - -func (g *GetUndiscriminatedOptionsRequest) UnmarshalJSON(data []byte) error { - type unmarshaler GetUndiscriminatedOptionsRequest - var body unmarshaler - if err := json.Unmarshal(data, &body); err != nil { - return err - } - *g = GetUndiscriminatedOptionsRequest(body) - g.dryRun = true - return nil -} - -func (g *GetUndiscriminatedOptionsRequest) MarshalJSON() ([]byte, error) { - type embed GetUndiscriminatedOptionsRequest - var marshaler = struct { - embed - DryRun bool `json:"dryRun"` - }{ - embed: embed(*g), - DryRun: true, - } - return json.Marshal(marshaler) -} - -type CreateOptionsResponse struct { - Type string - ok bool - Options *Options -} - -func NewCreateOptionsResponseWithOk() *CreateOptionsResponse { - return &CreateOptionsResponse{Type: "ok", ok: true} -} - -func NewCreateOptionsResponseFromOptions(value *Options) *CreateOptionsResponse { - return &CreateOptionsResponse{Type: "options", Options: value} -} - -func (c *CreateOptionsResponse) Ok() bool { - return c.ok -} - -func (c *CreateOptionsResponse) UnmarshalJSON(data []byte) error { - var unmarshaler struct { - Type string `json:"type"` - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - c.Type = unmarshaler.Type - switch unmarshaler.Type { - case "ok": - c.ok = true - case "options": - value := new(Options) - if err := json.Unmarshal(data, &value); err != nil { - return err - } - c.Options = value - } - return nil -} - -func (c CreateOptionsResponse) MarshalJSON() ([]byte, error) { - switch c.Type { - default: - return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) - case "ok": - var marshaler = struct { - Type string `json:"type"` - Ok bool `json:"value,omitempty"` - }{ - Type: c.Type, - Ok: true, - } - return json.Marshal(marshaler) - case "options": - var marshaler = struct { - Type string `json:"type"` - *Options - }{ - Type: c.Type, - Options: c.Options, - } - return json.Marshal(marshaler) - } -} - -type CreateOptionsResponseVisitor interface { - VisitOk(bool) error - VisitOptions(*Options) error -} - -func (c *CreateOptionsResponse) Accept(visitor CreateOptionsResponseVisitor) error { - switch c.Type { - default: - return fmt.Errorf("invalid type %s in %T", c.Type, c) - case "ok": - return visitor.VisitOk(c.ok) - case "options": - return visitor.VisitOptions(c.Options) - } -} - -type Options struct { - Values map[string]string `json:"values,omitempty" url:"values,omitempty"` - id string - enabled bool - - _rawJSON json.RawMessage -} - -func (o *Options) Id() string { - return o.id -} - -func (o *Options) Enabled() bool { - return o.enabled -} - -func (o *Options) UnmarshalJSON(data []byte) error { - type embed Options - var unmarshaler = struct { - embed - }{ - embed: embed(*o), - } - if err := json.Unmarshal(data, &unmarshaler); err != nil { - return err - } - *o = Options(unmarshaler.embed) - o.id = "options" - o.enabled = true - o._rawJSON = json.RawMessage(data) - return nil -} - -func (o *Options) MarshalJSON() ([]byte, error) { - type embed Options - var marshaler = struct { - embed - Id string `json:"id"` - Enabled bool `json:"enabled"` - }{ - embed: embed(*o), - Id: "options", - Enabled: true, - } - return json.Marshal(marshaler) -} - -func (o *Options) String() string { - if len(o._rawJSON) > 0 { - if value, err := core.StringifyJSON(o._rawJSON); err == nil { - return value - } - } - if value, err := core.StringifyJSON(o); err == nil { - return value - } - return fmt.Sprintf("%#v", o) -} - -type UndiscriminatedOptions struct { - typeName string - optionsStringLiteral string - trueBoolLiteral bool - StringStringMap map[string]string -} - -func NewUndiscriminatedOptionsWithOptionsStringLiteral() *UndiscriminatedOptions { - return &UndiscriminatedOptions{typeName: "optionsStringLiteral", optionsStringLiteral: "options"} -} - -func NewUndiscriminatedOptionsWithTrueBoolLiteral() *UndiscriminatedOptions { - return &UndiscriminatedOptions{typeName: "trueBoolLiteral", trueBoolLiteral: true} -} - -func NewUndiscriminatedOptionsFromStringStringMap(value map[string]string) *UndiscriminatedOptions { - return &UndiscriminatedOptions{typeName: "stringStringMap", StringStringMap: value} -} - -func (u *UndiscriminatedOptions) OptionsStringLiteral() string { - return u.optionsStringLiteral -} - -func (u *UndiscriminatedOptions) TrueBoolLiteral() bool { - return u.trueBoolLiteral -} - -func (u *UndiscriminatedOptions) UnmarshalJSON(data []byte) error { - var valueOptionsStringLiteral string - if err := json.Unmarshal(data, &valueOptionsStringLiteral); err == nil { - if valueOptionsStringLiteral == "options" { - u.typeName = "optionsStringLiteral" - u.optionsStringLiteral = valueOptionsStringLiteral - return nil - } - } - var valueTrueBoolLiteral bool - if err := json.Unmarshal(data, &valueTrueBoolLiteral); err == nil { - if valueTrueBoolLiteral == true { - u.typeName = "trueBoolLiteral" - u.trueBoolLiteral = valueTrueBoolLiteral - return nil - } - } - var valueStringStringMap map[string]string - if err := json.Unmarshal(data, &valueStringStringMap); err == nil { - u.typeName = "stringStringMap" - u.StringStringMap = valueStringStringMap - return nil - } - return fmt.Errorf("%s cannot be deserialized as a %T", data, u) -} - -func (u UndiscriminatedOptions) MarshalJSON() ([]byte, error) { - switch u.typeName { - default: - return nil, fmt.Errorf("invalid type %s in %T", u.typeName, u) - case "optionsStringLiteral": - return json.Marshal("options") - case "trueBoolLiteral": - return json.Marshal(true) - case "stringStringMap": - return json.Marshal(u.StringStringMap) - } -} - -type UndiscriminatedOptionsVisitor interface { - VisitOptionsStringLiteral(string) error - VisitTrueBoolLiteral(bool) error - VisitStringStringMap(map[string]string) error -} - -func (u *UndiscriminatedOptions) Accept(visitor UndiscriminatedOptionsVisitor) error { - switch u.typeName { - default: - return fmt.Errorf("invalid type %s in %T", u.typeName, u) - case "optionsStringLiteral": - return visitor.VisitOptionsStringLiteral(u.optionsStringLiteral) - case "trueBoolLiteral": - return visitor.VisitTrueBoolLiteral(u.trueBoolLiteral) - case "stringStringMap": - return visitor.VisitStringStringMap(u.StringStringMap) - } -} diff --git a/packages/cli/generation/ir-migrations/src/migrations/v34-to-v33/migrateFromV34ToV33.ts b/packages/cli/generation/ir-migrations/src/migrations/v34-to-v33/migrateFromV34ToV33.ts index 9362450f532..c6ca95de74b 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v34-to-v33/migrateFromV34ToV33.ts +++ b/packages/cli/generation/ir-migrations/src/migrations/v34-to-v33/migrateFromV34ToV33.ts @@ -9,7 +9,7 @@ export const V34_TO_V33_MIGRATION: IrMigration< IrVersions.V33.ir.IntermediateRepresentation > = { laterVersion: "v34", - earlierVersion: "V33", + earlierVersion: "v33", firstGeneratorVersionToConsumeNewIR: { [GeneratorName.TYPESCRIPT_NODE_SDK]: GeneratorWasNeverUpdatedToConsumeNewIR, [GeneratorName.TYPESCRIPT_BROWSER_SDK]: GeneratorWasNeverUpdatedToConsumeNewIR, diff --git a/packages/seed/src/commands/test/testWorkspaceFixtures.ts b/packages/seed/src/commands/test/testWorkspaceFixtures.ts index 90cfc797d90..45dc47fdc3e 100644 --- a/packages/seed/src/commands/test/testWorkspaceFixtures.ts +++ b/packages/seed/src/commands/test/testWorkspaceFixtures.ts @@ -395,7 +395,7 @@ async function testWithWriteToDisk({ const command = await loggingExeca( taskContext.logger, "docker", - ["exec", script.containerId, "/bin/bash", "-c", `chmod +x /${workDir}/test.sh && /${workDir}/test.sh`], + ["exec", script.containerId, "/bin/sh", "-c", `chmod +x /${workDir}/test.sh && /${workDir}/test.sh`], { doNotPipeOutput: true, reject: false diff --git a/generators/go/seed/sdk/bytes/.github/workflows/ci.yml b/seed/go-fiber/alias/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/bytes/.github/workflows/ci.yml rename to seed/go-fiber/alias/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/bytes/core/stringer.go b/seed/go-fiber/alias/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/stringer.go rename to seed/go-fiber/alias/core/stringer.go diff --git a/generators/go/seed/sdk/enum/core/time.go b/seed/go-fiber/alias/core/time.go similarity index 100% rename from generators/go/seed/sdk/enum/core/time.go rename to seed/go-fiber/alias/core/time.go diff --git a/seed/go-fiber/alias/go.mod b/seed/go-fiber/alias/go.mod new file mode 100644 index 00000000000..677c30f3e8a --- /dev/null +++ b/seed/go-fiber/alias/go.mod @@ -0,0 +1,3 @@ +module github.com/alias/fern + +go 1.13 diff --git a/generators/go/seed/sdk/bytes/snippet.json b/seed/go-fiber/alias/go.sum similarity index 100% rename from generators/go/seed/sdk/bytes/snippet.json rename to seed/go-fiber/alias/go.sum diff --git a/generators/go/seed/sdk/enum/snippet.json b/seed/go-fiber/alias/snippet.json similarity index 100% rename from generators/go/seed/sdk/enum/snippet.json rename to seed/go-fiber/alias/snippet.json diff --git a/seed/go-fiber/alias/types.go b/seed/go-fiber/alias/types.go new file mode 100644 index 00000000000..4289257179d --- /dev/null +++ b/seed/go-fiber/alias/types.go @@ -0,0 +1,27 @@ +// This file was auto-generated by Fern from our API Definition. + +package alias + +import ( + fmt "fmt" + core "github.com/alias/fern/core" +) + +// Object is an alias for a type. +type Object = *Type + +// A simple type with just a name. +type Type struct { + Id TypeId `json:"id" url:"id"` + Name string `json:"name" url:"name"` +} + +func (t *Type) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +// An alias for type IDs. +type TypeId = string diff --git a/generators/go/seed/sdk/enum/.github/workflows/ci.yml b/seed/go-fiber/api-wide-base-path/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/enum/.github/workflows/ci.yml rename to seed/go-fiber/api-wide-base-path/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/enum/core/stringer.go b/seed/go-fiber/api-wide-base-path/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/enum/core/stringer.go rename to seed/go-fiber/api-wide-base-path/core/stringer.go diff --git a/generators/go/seed/sdk/file-upload/core/time.go b/seed/go-fiber/api-wide-base-path/core/time.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/time.go rename to seed/go-fiber/api-wide-base-path/core/time.go diff --git a/seed/go-fiber/api-wide-base-path/go.mod b/seed/go-fiber/api-wide-base-path/go.mod new file mode 100644 index 00000000000..a1df980a794 --- /dev/null +++ b/seed/go-fiber/api-wide-base-path/go.mod @@ -0,0 +1,3 @@ +module github.com/api-wide-base-path/fern + +go 1.13 diff --git a/generators/go/seed/sdk/file-upload/snippet.json b/seed/go-fiber/api-wide-base-path/go.sum similarity index 100% rename from generators/go/seed/sdk/file-upload/snippet.json rename to seed/go-fiber/api-wide-base-path/go.sum diff --git a/generators/go/seed/sdk/idempotency-headers/snippet.json b/seed/go-fiber/api-wide-base-path/snippet.json similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/snippet.json rename to seed/go-fiber/api-wide-base-path/snippet.json diff --git a/generators/go/seed/sdk/file-upload/.github/workflows/ci.yml b/seed/go-fiber/audiences/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/file-upload/.github/workflows/ci.yml rename to seed/go-fiber/audiences/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/file-upload/core/stringer.go b/seed/go-fiber/audiences/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/stringer.go rename to seed/go-fiber/audiences/core/stringer.go diff --git a/generators/go/seed/sdk/idempotency-headers/core/time.go b/seed/go-fiber/audiences/core/time.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/time.go rename to seed/go-fiber/audiences/core/time.go diff --git a/seed/go-fiber/audiences/foldera/service.go b/seed/go-fiber/audiences/foldera/service.go new file mode 100644 index 00000000000..8b478e219ac --- /dev/null +++ b/seed/go-fiber/audiences/foldera/service.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package foldera + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + folderb "github.com/audiences/fern/folderb" +) + +type Response struct { + Foo *folderb.Foo `json:"foo,omitempty" url:"foo,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} diff --git a/seed/go-fiber/audiences/folderb/types.go b/seed/go-fiber/audiences/folderb/types.go new file mode 100644 index 00000000000..30966723530 --- /dev/null +++ b/seed/go-fiber/audiences/folderb/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderb + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + folderc "github.com/audiences/fern/folderc" +) + +type Foo struct { + Foo *folderc.Foo `json:"foo,omitempty" url:"foo,omitempty"` +} + +func (f *Foo) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-fiber/audiences/folderc/types.go b/seed/go-fiber/audiences/folderc/types.go new file mode 100644 index 00000000000..9a418f1b747 --- /dev/null +++ b/seed/go-fiber/audiences/folderc/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderc + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + uuid "github.com/google/uuid" +) + +type Foo struct { + BarProperty uuid.UUID `json:"bar_property" url:"bar_property"` +} + +func (f *Foo) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-fiber/audiences/foo.go b/seed/go-fiber/audiences/foo.go new file mode 100644 index 00000000000..74deeefd0b3 --- /dev/null +++ b/seed/go-fiber/audiences/foo.go @@ -0,0 +1,27 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type FindRequest struct { + OptionalString OptionalString `query:"optionalString"` + PublicProperty *string `json:"publicProperty,omitempty" url:"publicProperty,omitempty"` + PrivateProperty *int `json:"privateProperty,omitempty" url:"privateProperty,omitempty"` +} + +type ImportingType struct { + Imported Imported `json:"imported" url:"imported"` +} + +func (i *ImportingType) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type OptionalString = *string diff --git a/seed/go-fiber/audiences/go.mod b/seed/go-fiber/audiences/go.mod new file mode 100644 index 00000000000..be4474586bd --- /dev/null +++ b/seed/go-fiber/audiences/go.mod @@ -0,0 +1,5 @@ +module github.com/audiences/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-fiber/audiences/go.sum b/seed/go-fiber/audiences/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-fiber/audiences/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/generators/go/seed/sdk/literal-headers/snippet.json b/seed/go-fiber/audiences/snippet.json similarity index 100% rename from generators/go/seed/sdk/literal-headers/snippet.json rename to seed/go-fiber/audiences/snippet.json diff --git a/seed/go-fiber/audiences/types.go b/seed/go-fiber/audiences/types.go new file mode 100644 index 00000000000..c6cb753155c --- /dev/null +++ b/seed/go-fiber/audiences/types.go @@ -0,0 +1,22 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type Imported = string + +type FilteredType struct { + PublicProperty *string `json:"public_property,omitempty" url:"public_property,omitempty"` + PrivateProperty int `json:"private_property" url:"private_property"` +} + +func (f *FilteredType) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/generators/go/seed/sdk/idempotency-headers/.github/workflows/ci.yml b/seed/go-fiber/auth-environment-variables/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/.github/workflows/ci.yml rename to seed/go-fiber/auth-environment-variables/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/idempotency-headers/core/stringer.go b/seed/go-fiber/auth-environment-variables/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/stringer.go rename to seed/go-fiber/auth-environment-variables/core/stringer.go diff --git a/generators/go/seed/sdk/literal-headers/core/time.go b/seed/go-fiber/auth-environment-variables/core/time.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/time.go rename to seed/go-fiber/auth-environment-variables/core/time.go diff --git a/seed/go-fiber/auth-environment-variables/go.mod b/seed/go-fiber/auth-environment-variables/go.mod new file mode 100644 index 00000000000..a06655610bf --- /dev/null +++ b/seed/go-fiber/auth-environment-variables/go.mod @@ -0,0 +1,3 @@ +module github.com/auth-environment-variables/fern + +go 1.13 diff --git a/generators/go/seed/sdk/literal/snippet.json b/seed/go-fiber/auth-environment-variables/go.sum similarity index 100% rename from generators/go/seed/sdk/literal/snippet.json rename to seed/go-fiber/auth-environment-variables/go.sum diff --git a/generators/go/seed/sdk/plain-text/snippet.json b/seed/go-fiber/auth-environment-variables/snippet.json similarity index 100% rename from generators/go/seed/sdk/plain-text/snippet.json rename to seed/go-fiber/auth-environment-variables/snippet.json diff --git a/generators/go/seed/sdk/literal-headers/.github/workflows/ci.yml b/seed/go-fiber/basic-auth/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/literal-headers/.github/workflows/ci.yml rename to seed/go-fiber/basic-auth/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/literal-headers/core/stringer.go b/seed/go-fiber/basic-auth/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/stringer.go rename to seed/go-fiber/basic-auth/core/stringer.go diff --git a/generators/go/seed/sdk/literal/core/time.go b/seed/go-fiber/basic-auth/core/time.go similarity index 100% rename from generators/go/seed/sdk/literal/core/time.go rename to seed/go-fiber/basic-auth/core/time.go diff --git a/seed/go-fiber/basic-auth/go.mod b/seed/go-fiber/basic-auth/go.mod new file mode 100644 index 00000000000..91034c571ad --- /dev/null +++ b/seed/go-fiber/basic-auth/go.mod @@ -0,0 +1,3 @@ +module github.com/basic-auth/fern + +go 1.13 diff --git a/generators/go/seed/sdk/query-parameters/snippet.json b/seed/go-fiber/basic-auth/go.sum similarity index 100% rename from generators/go/seed/sdk/query-parameters/snippet.json rename to seed/go-fiber/basic-auth/go.sum diff --git a/generators/go/seed/sdk/response-property/snippet.json b/seed/go-fiber/basic-auth/snippet.json similarity index 100% rename from generators/go/seed/sdk/response-property/snippet.json rename to seed/go-fiber/basic-auth/snippet.json diff --git a/seed/go-fiber/basic-auth/types.go b/seed/go-fiber/basic-auth/types.go new file mode 100644 index 00000000000..b1af9f28cad --- /dev/null +++ b/seed/go-fiber/basic-auth/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package basicauth + +import ( + fmt "fmt" + core "github.com/basic-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/generators/go/seed/sdk/literal/.github/workflows/ci.yml b/seed/go-fiber/bearer-token-environment-variable/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/literal/.github/workflows/ci.yml rename to seed/go-fiber/bearer-token-environment-variable/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/literal/core/stringer.go b/seed/go-fiber/bearer-token-environment-variable/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/literal/core/stringer.go rename to seed/go-fiber/bearer-token-environment-variable/core/stringer.go diff --git a/generators/go/seed/sdk/plain-text/core/time.go b/seed/go-fiber/bearer-token-environment-variable/core/time.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/time.go rename to seed/go-fiber/bearer-token-environment-variable/core/time.go diff --git a/seed/go-fiber/bearer-token-environment-variable/go.mod b/seed/go-fiber/bearer-token-environment-variable/go.mod new file mode 100644 index 00000000000..cf3bd5f35fb --- /dev/null +++ b/seed/go-fiber/bearer-token-environment-variable/go.mod @@ -0,0 +1,3 @@ +module github.com/bearer-token-environment-variable/fern + +go 1.13 diff --git a/generators/go/seed/sdk/streaming/snippet.json b/seed/go-fiber/bearer-token-environment-variable/go.sum similarity index 100% rename from generators/go/seed/sdk/streaming/snippet.json rename to seed/go-fiber/bearer-token-environment-variable/go.sum diff --git a/seed/go-fiber/bearer-token-environment-variable/snippet.json b/seed/go-fiber/bearer-token-environment-variable/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/plain-text/.github/workflows/ci.yml b/seed/go-fiber/bytes/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/plain-text/.github/workflows/ci.yml rename to seed/go-fiber/bytes/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/plain-text/core/stringer.go b/seed/go-fiber/bytes/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/stringer.go rename to seed/go-fiber/bytes/core/stringer.go diff --git a/generators/go/seed/sdk/query-parameters/core/time.go b/seed/go-fiber/bytes/core/time.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/time.go rename to seed/go-fiber/bytes/core/time.go diff --git a/seed/go-fiber/bytes/go.mod b/seed/go-fiber/bytes/go.mod new file mode 100644 index 00000000000..eaa9f6476d5 --- /dev/null +++ b/seed/go-fiber/bytes/go.mod @@ -0,0 +1,3 @@ +module github.com/bytes/fern + +go 1.13 diff --git a/seed/go-fiber/bytes/go.sum b/seed/go-fiber/bytes/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/bytes/snippet.json b/seed/go-fiber/bytes/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/query-parameters/.github/workflows/ci.yml b/seed/go-fiber/circular-references/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/query-parameters/.github/workflows/ci.yml rename to seed/go-fiber/circular-references/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/query-parameters/core/stringer.go b/seed/go-fiber/circular-references/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/stringer.go rename to seed/go-fiber/circular-references/core/stringer.go diff --git a/generators/go/seed/sdk/response-property/core/time.go b/seed/go-fiber/circular-references/core/time.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/time.go rename to seed/go-fiber/circular-references/core/time.go diff --git a/seed/go-fiber/circular-references/go.mod b/seed/go-fiber/circular-references/go.mod new file mode 100644 index 00000000000..acd7c9ec857 --- /dev/null +++ b/seed/go-fiber/circular-references/go.mod @@ -0,0 +1,3 @@ +module github.com/circular-references/fern + +go 1.13 diff --git a/seed/go-fiber/circular-references/go.sum b/seed/go-fiber/circular-references/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/circular-references/snippet.json b/seed/go-fiber/circular-references/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/circular-references/types.go b/seed/go-fiber/circular-references/types.go new file mode 100644 index 00000000000..63ef560c114 --- /dev/null +++ b/seed/go-fiber/circular-references/types.go @@ -0,0 +1,265 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/circular-references/fern/core" +) + +type ImportingA struct { + A *A `json:"a,omitempty" url:"a,omitempty"` +} + +func (i *ImportingA) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type RootType struct { + S string `json:"s" url:"s"` +} + +func (r *RootType) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type A struct { + S string `json:"s" url:"s"` +} + +func (a *A) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type ContainerValue struct { + Type string + List []*FieldValue + Optional *FieldValue +} + +func NewContainerValueFromList(value []*FieldValue) *ContainerValue { + return &ContainerValue{Type: "list", List: value} +} + +func NewContainerValueFromOptional(value *FieldValue) *ContainerValue { + return &ContainerValue{Type: "optional", Optional: value} +} + +func (c *ContainerValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + c.Type = unmarshaler.Type + switch unmarshaler.Type { + case "list": + var valueUnmarshaler struct { + List []*FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.List = valueUnmarshaler.List + case "optional": + var valueUnmarshaler struct { + Optional *FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.Optional = valueUnmarshaler.Optional + } + return nil +} + +func (c ContainerValue) MarshalJSON() ([]byte, error) { + switch c.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + var marshaler = struct { + Type string `json:"type"` + List []*FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + List: c.List, + } + return json.Marshal(marshaler) + case "optional": + var marshaler = struct { + Type string `json:"type"` + Optional *FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + Optional: c.Optional, + } + return json.Marshal(marshaler) + } +} + +type ContainerValueVisitor interface { + VisitList([]*FieldValue) error + VisitOptional(*FieldValue) error +} + +func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { + switch c.Type { + default: + return fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + return visitor.VisitList(c.List) + case "optional": + return visitor.VisitOptional(c.Optional) + } +} + +type FieldValue struct { + Type string + PrimitiveValue PrimitiveValue + ObjectValue *ObjectValue + ContainerValue *ContainerValue +} + +func NewFieldValueFromPrimitiveValue(value PrimitiveValue) *FieldValue { + return &FieldValue{Type: "primitive_value", PrimitiveValue: value} +} + +func NewFieldValueFromObjectValue(value *ObjectValue) *FieldValue { + return &FieldValue{Type: "object_value", ObjectValue: value} +} + +func NewFieldValueFromContainerValue(value *ContainerValue) *FieldValue { + return &FieldValue{Type: "container_value", ContainerValue: value} +} + +func (f *FieldValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + f.Type = unmarshaler.Type + switch unmarshaler.Type { + case "primitive_value": + var valueUnmarshaler struct { + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.PrimitiveValue = valueUnmarshaler.PrimitiveValue + case "object_value": + value := new(ObjectValue) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + f.ObjectValue = value + case "container_value": + var valueUnmarshaler struct { + ContainerValue *ContainerValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.ContainerValue = valueUnmarshaler.ContainerValue + } + return nil +} + +func (f FieldValue) MarshalJSON() ([]byte, error) { + switch f.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + var marshaler = struct { + Type string `json:"type"` + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + }{ + Type: f.Type, + PrimitiveValue: f.PrimitiveValue, + } + return json.Marshal(marshaler) + case "object_value": + var marshaler = struct { + Type string `json:"type"` + *ObjectValue + }{ + Type: f.Type, + ObjectValue: f.ObjectValue, + } + return json.Marshal(marshaler) + case "container_value": + var marshaler = struct { + Type string `json:"type"` + ContainerValue *ContainerValue `json:"value,omitempty"` + }{ + Type: f.Type, + ContainerValue: f.ContainerValue, + } + return json.Marshal(marshaler) + } +} + +type FieldValueVisitor interface { + VisitPrimitiveValue(PrimitiveValue) error + VisitObjectValue(*ObjectValue) error + VisitContainerValue(*ContainerValue) error +} + +func (f *FieldValue) Accept(visitor FieldValueVisitor) error { + switch f.Type { + default: + return fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + return visitor.VisitPrimitiveValue(f.PrimitiveValue) + case "object_value": + return visitor.VisitObjectValue(f.ObjectValue) + case "container_value": + return visitor.VisitContainerValue(f.ContainerValue) + } +} + +type ObjectValue struct { +} + +func (o *ObjectValue) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type PrimitiveValue string + +const ( + PrimitiveValueString PrimitiveValue = "STRING" + PrimitiveValueNumber PrimitiveValue = "NUMBER" +) + +func NewPrimitiveValueFromString(s string) (PrimitiveValue, error) { + switch s { + case "STRING": + return PrimitiveValueString, nil + case "NUMBER": + return PrimitiveValueNumber, nil + } + var t PrimitiveValue + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (p PrimitiveValue) Ptr() *PrimitiveValue { + return &p +} diff --git a/generators/go/seed/sdk/response-property/.github/workflows/ci.yml b/seed/go-fiber/custom-auth/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/response-property/.github/workflows/ci.yml rename to seed/go-fiber/custom-auth/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/response-property/core/stringer.go b/seed/go-fiber/custom-auth/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/stringer.go rename to seed/go-fiber/custom-auth/core/stringer.go diff --git a/generators/go/seed/sdk/streaming/core/time.go b/seed/go-fiber/custom-auth/core/time.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/time.go rename to seed/go-fiber/custom-auth/core/time.go diff --git a/seed/go-fiber/custom-auth/go.mod b/seed/go-fiber/custom-auth/go.mod new file mode 100644 index 00000000000..513db0ccbe2 --- /dev/null +++ b/seed/go-fiber/custom-auth/go.mod @@ -0,0 +1,3 @@ +module github.com/custom-auth/fern + +go 1.13 diff --git a/seed/go-fiber/custom-auth/go.sum b/seed/go-fiber/custom-auth/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/custom-auth/snippet.json b/seed/go-fiber/custom-auth/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/custom-auth/types.go b/seed/go-fiber/custom-auth/types.go new file mode 100644 index 00000000000..c5866144f74 --- /dev/null +++ b/seed/go-fiber/custom-auth/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package customauth + +import ( + fmt "fmt" + core "github.com/custom-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/generators/go/seed/sdk/streaming/.github/workflows/ci.yml b/seed/go-fiber/enum/.github/workflows/ci.yml similarity index 100% rename from generators/go/seed/sdk/streaming/.github/workflows/ci.yml rename to seed/go-fiber/enum/.github/workflows/ci.yml diff --git a/generators/go/seed/sdk/streaming/core/stringer.go b/seed/go-fiber/enum/core/stringer.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/stringer.go rename to seed/go-fiber/enum/core/stringer.go diff --git a/seed/go-fiber/enum/core/time.go b/seed/go-fiber/enum/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/enum/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/enum/go.mod b/seed/go-fiber/enum/go.mod new file mode 100644 index 00000000000..f3d0e706536 --- /dev/null +++ b/seed/go-fiber/enum/go.mod @@ -0,0 +1,3 @@ +module github.com/enum/fern + +go 1.13 diff --git a/seed/go-fiber/enum/go.sum b/seed/go-fiber/enum/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/enum/inlined_request.go b/seed/go-fiber/enum/inlined_request.go similarity index 63% rename from generators/go/seed/sdk/enum/inlined_request.go rename to seed/go-fiber/enum/inlined_request.go index 0d604121de8..da27590d949 100644 --- a/generators/go/seed/sdk/enum/inlined_request.go +++ b/seed/go-fiber/enum/inlined_request.go @@ -3,5 +3,5 @@ package enum type SendEnumInlinedRequest struct { - Value *Operand `json:"value,omitempty" url:"value,omitempty"` + Operand *Operand `json:"operand,omitempty" url:"operand,omitempty"` } diff --git a/generators/go/seed/sdk/enum/query_param.go b/seed/go-fiber/enum/query_param.go similarity index 63% rename from generators/go/seed/sdk/enum/query_param.go rename to seed/go-fiber/enum/query_param.go index dfa5b89f0d2..656161de39d 100644 --- a/generators/go/seed/sdk/enum/query_param.go +++ b/seed/go-fiber/enum/query_param.go @@ -3,9 +3,9 @@ package enum type SendEnumAsQueryParamRequest struct { - Value *Operand `json:"-" url:"value,omitempty"` + Operand *Operand `query:"operand"` } type SendEnumListAsQueryParamRequest struct { - Value []*Operand `json:"-" url:"value,omitempty"` + Operand []*Operand `query:"operand"` } diff --git a/seed/go-fiber/enum/snippet.json b/seed/go-fiber/enum/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/enum/types.go b/seed/go-fiber/enum/types.go similarity index 100% rename from generators/go/seed/sdk/enum/types.go rename to seed/go-fiber/enum/types.go diff --git a/seed/go-fiber/error-property/.github/workflows/ci.yml b/seed/go-fiber/error-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/error-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/error-property/core/stringer.go b/seed/go-fiber/error-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/error-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/error-property/core/time.go b/seed/go-fiber/error-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/error-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/error-property/go.mod b/seed/go-fiber/error-property/go.mod new file mode 100644 index 00000000000..f48cabcb94b --- /dev/null +++ b/seed/go-fiber/error-property/go.mod @@ -0,0 +1,3 @@ +module github.com/error-property/fern + +go 1.13 diff --git a/seed/go-fiber/error-property/go.sum b/seed/go-fiber/error-property/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/error-property/snippet.json b/seed/go-fiber/error-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/error-property/types.go b/seed/go-fiber/error-property/types.go new file mode 100644 index 00000000000..518904ff7de --- /dev/null +++ b/seed/go-fiber/error-property/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package errorproperty + +import ( + fmt "fmt" + core "github.com/error-property/fern/core" +) + +type PropertyBasedErrorTestBody struct { + Message string `json:"message" url:"message"` +} + +func (p *PropertyBasedErrorTestBody) String() string { + if value, err := core.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} diff --git a/seed/go-fiber/examples/.github/workflows/ci.yml b/seed/go-fiber/examples/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/examples/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/examples/commons/types.go b/seed/go-fiber/examples/commons/types.go new file mode 100644 index 00000000000..14cad3066ac --- /dev/null +++ b/seed/go-fiber/examples/commons/types.go @@ -0,0 +1,190 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/examples/fern/core" +) + +type Data struct { + Type string + String string + Base64 []byte +} + +func NewDataFromString(value string) *Data { + return &Data{Type: "string", String: value} +} + +func NewDataFromBase64(value []byte) *Data { + return &Data{Type: "base64", Base64: value} +} + +func (d *Data) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + d.Type = unmarshaler.Type + switch unmarshaler.Type { + case "string": + var valueUnmarshaler struct { + String string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.String = valueUnmarshaler.String + case "base64": + var valueUnmarshaler struct { + Base64 []byte `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.Base64 = valueUnmarshaler.Base64 + } + return nil +} + +func (d Data) MarshalJSON() ([]byte, error) { + switch d.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + var marshaler = struct { + Type string `json:"type"` + String string `json:"value"` + }{ + Type: d.Type, + String: d.String, + } + return json.Marshal(marshaler) + case "base64": + var marshaler = struct { + Type string `json:"type"` + Base64 []byte `json:"value"` + }{ + Type: d.Type, + Base64: d.Base64, + } + return json.Marshal(marshaler) + } +} + +type DataVisitor interface { + VisitString(string) error + VisitBase64([]byte) error +} + +func (d *Data) Accept(visitor DataVisitor) error { + switch d.Type { + default: + return fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + return visitor.VisitString(d.String) + case "base64": + return visitor.VisitBase64(d.Base64) + } +} + +type EventInfo struct { + Type string + Metadata *Metadata + Tag Tag +} + +func NewEventInfoFromMetadata(value *Metadata) *EventInfo { + return &EventInfo{Type: "metadata", Metadata: value} +} + +func NewEventInfoFromTag(value Tag) *EventInfo { + return &EventInfo{Type: "tag", Tag: value} +} + +func (e *EventInfo) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "metadata": + value := new(Metadata) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Metadata = value + case "tag": + var valueUnmarshaler struct { + Tag Tag `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + e.Tag = valueUnmarshaler.Tag + } + return nil +} + +func (e EventInfo) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + var marshaler = struct { + Type string `json:"type"` + *Metadata + }{ + Type: e.Type, + Metadata: e.Metadata, + } + return json.Marshal(marshaler) + case "tag": + var marshaler = struct { + Type string `json:"type"` + Tag Tag `json:"value"` + }{ + Type: e.Type, + Tag: e.Tag, + } + return json.Marshal(marshaler) + } +} + +type EventInfoVisitor interface { + VisitMetadata(*Metadata) error + VisitTag(Tag) error +} + +func (e *EventInfo) Accept(visitor EventInfoVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + return visitor.VisitMetadata(e.Metadata) + case "tag": + return visitor.VisitTag(e.Tag) + } +} + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` + JsonString *string `json:"jsonString,omitempty" url:"jsonString,omitempty"` +} + +func (m *Metadata) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Tag = string diff --git a/seed/go-fiber/examples/core/stringer.go b/seed/go-fiber/examples/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/examples/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/examples/core/time.go b/seed/go-fiber/examples/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/examples/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/examples/file/service.go b/seed/go-fiber/examples/file/service.go new file mode 100644 index 00000000000..5253d0234d9 --- /dev/null +++ b/seed/go-fiber/examples/file/service.go @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type GetFileRequest struct { +} diff --git a/seed/go-fiber/examples/file/types.go b/seed/go-fiber/examples/file/types.go new file mode 100644 index 00000000000..edc509f893e --- /dev/null +++ b/seed/go-fiber/examples/file/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type Filename = string diff --git a/seed/go-fiber/examples/go.mod b/seed/go-fiber/examples/go.mod new file mode 100644 index 00000000000..aa33f862049 --- /dev/null +++ b/seed/go-fiber/examples/go.mod @@ -0,0 +1,5 @@ +module github.com/examples/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-fiber/examples/go.sum b/seed/go-fiber/examples/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-fiber/examples/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-fiber/examples/service.go b/seed/go-fiber/examples/service.go new file mode 100644 index 00000000000..61fc7f66e37 --- /dev/null +++ b/seed/go-fiber/examples/service.go @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +type GetMetadataRequest struct { + XApiVersion string `header:"X-API-Version"` + Shallow *bool `query:"shallow"` + Tag []*string `query:"tag"` +} diff --git a/seed/go-fiber/examples/snippet.json b/seed/go-fiber/examples/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/examples/types.go b/seed/go-fiber/examples/types.go new file mode 100644 index 00000000000..234f4daf1bf --- /dev/null +++ b/seed/go-fiber/examples/types.go @@ -0,0 +1,652 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + fmt "fmt" + commons "github.com/examples/fern/commons" + core "github.com/examples/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type Actor struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` +} + +func (a *Actor) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type Actress struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` +} + +func (a *Actress) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type CastMember struct { + typeName string + Actor *Actor + Actress *Actress + StuntDouble *StuntDouble +} + +func NewCastMemberFromActor(value *Actor) *CastMember { + return &CastMember{typeName: "actor", Actor: value} +} + +func NewCastMemberFromActress(value *Actress) *CastMember { + return &CastMember{typeName: "actress", Actress: value} +} + +func NewCastMemberFromStuntDouble(value *StuntDouble) *CastMember { + return &CastMember{typeName: "stuntDouble", StuntDouble: value} +} + +func (c *CastMember) UnmarshalJSON(data []byte) error { + valueActor := new(Actor) + if err := json.Unmarshal(data, &valueActor); err == nil { + c.typeName = "actor" + c.Actor = valueActor + return nil + } + valueActress := new(Actress) + if err := json.Unmarshal(data, &valueActress); err == nil { + c.typeName = "actress" + c.Actress = valueActress + return nil + } + valueStuntDouble := new(StuntDouble) + if err := json.Unmarshal(data, &valueStuntDouble); err == nil { + c.typeName = "stuntDouble" + c.StuntDouble = valueStuntDouble + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, c) +} + +func (c CastMember) MarshalJSON() ([]byte, error) { + switch c.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return json.Marshal(c.Actor) + case "actress": + return json.Marshal(c.Actress) + case "stuntDouble": + return json.Marshal(c.StuntDouble) + } +} + +type CastMemberVisitor interface { + VisitActor(*Actor) error + VisitActress(*Actress) error + VisitStuntDouble(*StuntDouble) error +} + +func (c *CastMember) Accept(visitor CastMemberVisitor) error { + switch c.typeName { + default: + return fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return visitor.VisitActor(c.Actor) + case "actress": + return visitor.VisitActress(c.Actress) + case "stuntDouble": + return visitor.VisitStuntDouble(c.StuntDouble) + } +} + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` +} + +func (d *Directory) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type Exception struct { + Type string + Generic *ExceptionInfo + Timeout interface{} +} + +func NewExceptionFromGeneric(value *ExceptionInfo) *Exception { + return &Exception{Type: "generic", Generic: value} +} + +func NewExceptionFromTimeout(value interface{}) *Exception { + return &Exception{Type: "timeout", Timeout: value} +} + +func (e *Exception) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "generic": + value := new(ExceptionInfo) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Generic = value + case "timeout": + value := make(map[string]interface{}) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Timeout = value + } + return nil +} + +func (e Exception) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + var marshaler = struct { + Type string `json:"type"` + *ExceptionInfo + }{ + Type: e.Type, + ExceptionInfo: e.Generic, + } + return json.Marshal(marshaler) + case "timeout": + var marshaler = struct { + Type string `json:"type"` + Timeout interface{} `json:"timeout,omitempty"` + }{ + Type: e.Type, + Timeout: e.Timeout, + } + return json.Marshal(marshaler) + } +} + +type ExceptionVisitor interface { + VisitGeneric(*ExceptionInfo) error + VisitTimeout(interface{}) error +} + +func (e *Exception) Accept(visitor ExceptionVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + return visitor.VisitGeneric(e.Generic) + case "timeout": + return visitor.VisitTimeout(e.Timeout) + } +} + +type ExceptionInfo struct { + ExceptionType string `json:"exceptionType" url:"exceptionType"` + ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` + ExceptionStacktrace string `json:"exceptionStacktrace" url:"exceptionStacktrace"` +} + +func (e *ExceptionInfo) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type ExtendedMovie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + Cast []string `json:"cast,omitempty" url:"cast,omitempty"` + type_ string +} + +func (e *ExtendedMovie) Type() string { + return e.type_ +} + +func (e *ExtendedMovie) UnmarshalJSON(data []byte) error { + type embed ExtendedMovie + var unmarshaler = struct { + embed + }{ + embed: embed(*e), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *e = ExtendedMovie(unmarshaler.embed) + e.type_ = "movie" + return nil +} + +func (e *ExtendedMovie) MarshalJSON() ([]byte, error) { + type embed ExtendedMovie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*e), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (e *ExtendedMovie) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` +} + +func (f *File) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type Metadata struct { + Type string + Extra map[string]string + Tags []string + Html string + Markdown string +} + +func NewMetadataFromHtml(value string) *Metadata { + return &Metadata{Type: "html", Html: value} +} + +func NewMetadataFromMarkdown(value string) *Metadata { + return &Metadata{Type: "markdown", Markdown: value} +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + m.Type = unmarshaler.Type + m.Extra = unmarshaler.Extra + m.Tags = unmarshaler.Tags + switch unmarshaler.Type { + case "html": + var valueUnmarshaler struct { + Html string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Html = valueUnmarshaler.Html + case "markdown": + var valueUnmarshaler struct { + Markdown string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Markdown = valueUnmarshaler.Markdown + } + return nil +} + +func (m Metadata) MarshalJSON() ([]byte, error) { + switch m.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Html string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Html: m.Html, + } + return json.Marshal(marshaler) + case "markdown": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Markdown string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Markdown: m.Markdown, + } + return json.Marshal(marshaler) + } +} + +type MetadataVisitor interface { + VisitHtml(string) error + VisitMarkdown(string) error +} + +func (m *Metadata) Accept(visitor MetadataVisitor) error { + switch m.Type { + default: + return fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + return visitor.VisitHtml(m.Html) + case "markdown": + return visitor.VisitMarkdown(m.Markdown) + } +} + +type Migration struct { + Name string `json:"name" url:"name"` + Status MigrationStatus `json:"status,omitempty" url:"status,omitempty"` +} + +func (m *Migration) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MigrationStatus string + +const ( + // The migration is running. + MigrationStatusRunning MigrationStatus = "RUNNING" + // The migration failed. + MigrationStatusFailed MigrationStatus = "FAILED" + MigrationStatusFinished MigrationStatus = "FINISHED" +) + +func NewMigrationStatusFromString(s string) (MigrationStatus, error) { + switch s { + case "RUNNING": + return MigrationStatusRunning, nil + case "FAILED": + return MigrationStatusFailed, nil + case "FINISHED": + return MigrationStatusFinished, nil + } + var t MigrationStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (m MigrationStatus) Ptr() *MigrationStatus { + return &m +} + +type Moment struct { + Id uuid.UUID `json:"id" url:"id"` + Date time.Time `json:"date" url:"date" format:"date"` + Datetime time.Time `json:"datetime" url:"datetime"` +} + +func (m *Moment) UnmarshalJSON(data []byte) error { + type embed Moment + var unmarshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Moment(unmarshaler.embed) + m.Date = unmarshaler.Date.Time() + m.Datetime = unmarshaler.Datetime.Time() + return nil +} + +func (m *Moment) MarshalJSON() ([]byte, error) { + type embed Moment + var marshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + Date: core.NewDate(m.Date), + Datetime: core.NewDateTime(m.Datetime), + } + return json.Marshal(marshaler) +} + +func (m *Moment) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + type_ string +} + +func (m *Movie) Type() string { + return m.type_ +} + +func (m *Movie) UnmarshalJSON(data []byte) error { + type embed Movie + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Movie(unmarshaler.embed) + m.type_ = "movie" + return nil +} + +func (m *Movie) MarshalJSON() ([]byte, error) { + type embed Movie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*m), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string + +type Node struct { + Name string `json:"name" url:"name"` + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + Trees []*Tree `json:"trees,omitempty" url:"trees,omitempty"` +} + +func (n *Node) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Request struct { + Request interface{} `json:"request,omitempty" url:"request,omitempty"` +} + +func (r *Request) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Response struct { + Response interface{} `json:"response,omitempty" url:"response,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type StuntDouble struct { + Name string `json:"name" url:"name"` + ActorOrActressId string `json:"actorOrActressId" url:"actorOrActressId"` +} + +func (s *StuntDouble) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type Test struct { + Type string + And bool + Or bool +} + +func NewTestFromAnd(value bool) *Test { + return &Test{Type: "and", And: value} +} + +func NewTestFromOr(value bool) *Test { + return &Test{Type: "or", Or: value} +} + +func (t *Test) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + t.Type = unmarshaler.Type + switch unmarshaler.Type { + case "and": + var valueUnmarshaler struct { + And bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.And = valueUnmarshaler.And + case "or": + var valueUnmarshaler struct { + Or bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.Or = valueUnmarshaler.Or + } + return nil +} + +func (t Test) MarshalJSON() ([]byte, error) { + switch t.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + var marshaler = struct { + Type string `json:"type"` + And bool `json:"value"` + }{ + Type: t.Type, + And: t.And, + } + return json.Marshal(marshaler) + case "or": + var marshaler = struct { + Type string `json:"type"` + Or bool `json:"value"` + }{ + Type: t.Type, + Or: t.Or, + } + return json.Marshal(marshaler) + } +} + +type TestVisitor interface { + VisitAnd(bool) error + VisitOr(bool) error +} + +func (t *Test) Accept(visitor TestVisitor) error { + switch t.Type { + default: + return fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + return visitor.VisitAnd(t.And) + case "or": + return visitor.VisitOr(t.Or) + } +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` +} + +func (t *Tree) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-fiber/exhaustive/.github/workflows/ci.yml b/seed/go-fiber/exhaustive/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/exhaustive/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/exhaustive/core/stringer.go b/seed/go-fiber/exhaustive/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/exhaustive/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/exhaustive/core/time.go b/seed/go-fiber/exhaustive/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/exhaustive/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/exhaustive/endpoints/params.go b/seed/go-fiber/exhaustive/endpoints/params.go new file mode 100644 index 00000000000..2c9727eb1ab --- /dev/null +++ b/seed/go-fiber/exhaustive/endpoints/params.go @@ -0,0 +1,17 @@ +// This file was auto-generated by Fern from our API Definition. + +package endpoints + +type GetWithMultipleQuery struct { + Query []string `query:"query"` + Numer []int `query:"numer"` +} + +type GetWithPathAndQuery struct { + Query string `query:"query"` +} + +type GetWithQuery struct { + Query string `query:"query"` + Number int `query:"number"` +} diff --git a/seed/go-fiber/exhaustive/go.mod b/seed/go-fiber/exhaustive/go.mod new file mode 100644 index 00000000000..664aa32c9a0 --- /dev/null +++ b/seed/go-fiber/exhaustive/go.mod @@ -0,0 +1,5 @@ +module github.com/exhaustive/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-fiber/exhaustive/go.sum b/seed/go-fiber/exhaustive/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-fiber/exhaustive/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-fiber/exhaustive/inlined_requests.go b/seed/go-fiber/exhaustive/inlined_requests.go new file mode 100644 index 00000000000..19b5799d8e5 --- /dev/null +++ b/seed/go-fiber/exhaustive/inlined_requests.go @@ -0,0 +1,13 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive + +import ( + types "github.com/exhaustive/fern/types" +) + +type PostWithObjectBody struct { + String string `json:"string" url:"string"` + Integer int `json:"integer" url:"integer"` + NestedObject *types.ObjectWithOptionalField `json:"NestedObject,omitempty" url:"NestedObject,omitempty"` +} diff --git a/seed/go-fiber/exhaustive/req_with_headers.go b/seed/go-fiber/exhaustive/req_with_headers.go new file mode 100644 index 00000000000..74b85337067 --- /dev/null +++ b/seed/go-fiber/exhaustive/req_with_headers.go @@ -0,0 +1,25 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive + +import ( + json "encoding/json" +) + +type ReqWithHeaders struct { + XTestEndpointHeader string `header:"X-TEST-ENDPOINT-HEADER"` + Body string `json:"-" url:"-"` +} + +func (r *ReqWithHeaders) UnmarshalJSON(data []byte) error { + var body string + if err := json.Unmarshal(data, &body); err != nil { + return err + } + r.Body = body + return nil +} + +func (r *ReqWithHeaders) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Body) +} diff --git a/seed/go-fiber/exhaustive/snippet.json b/seed/go-fiber/exhaustive/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/exhaustive/types.go b/seed/go-fiber/exhaustive/types.go new file mode 100644 index 00000000000..6457cdb8c9f --- /dev/null +++ b/seed/go-fiber/exhaustive/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive + +import ( + fmt "fmt" + core "github.com/exhaustive/fern/core" +) + +type BadObjectRequestInfo struct { + Message string `json:"message" url:"message"` +} + +func (b *BadObjectRequestInfo) String() string { + if value, err := core.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} diff --git a/seed/go-fiber/exhaustive/types/types.go b/seed/go-fiber/exhaustive/types/types.go new file mode 100644 index 00000000000..90df02f72d9 --- /dev/null +++ b/seed/go-fiber/exhaustive/types/types.go @@ -0,0 +1,243 @@ +// This file was auto-generated by Fern from our API Definition. + +package types + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/exhaustive/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type WeatherReport string + +const ( + WeatherReportSunny WeatherReport = "SUNNY" + WeatherReportCloudy WeatherReport = "CLOUDY" + WeatherReportRaining WeatherReport = "RAINING" + WeatherReportSnowing WeatherReport = "SNOWING" +) + +func NewWeatherReportFromString(s string) (WeatherReport, error) { + switch s { + case "SUNNY": + return WeatherReportSunny, nil + case "CLOUDY": + return WeatherReportCloudy, nil + case "RAINING": + return WeatherReportRaining, nil + case "SNOWING": + return WeatherReportSnowing, nil + } + var t WeatherReport + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (w WeatherReport) Ptr() *WeatherReport { + return &w +} + +type NestedObjectWithOptionalField struct { + String *string `json:"string,omitempty" url:"string,omitempty"` + NestedObject *ObjectWithOptionalField `json:"NestedObject,omitempty" url:"NestedObject,omitempty"` +} + +func (n *NestedObjectWithOptionalField) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type NestedObjectWithRequiredField struct { + String string `json:"string" url:"string"` + NestedObject *ObjectWithOptionalField `json:"NestedObject,omitempty" url:"NestedObject,omitempty"` +} + +func (n *NestedObjectWithRequiredField) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type ObjectWithMapOfMap struct { + Map map[string]map[string]string `json:"map,omitempty" url:"map,omitempty"` +} + +func (o *ObjectWithMapOfMap) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type ObjectWithOptionalField struct { + String *string `json:"string,omitempty" url:"string,omitempty"` + Integer *int `json:"integer,omitempty" url:"integer,omitempty"` + Long *int64 `json:"long,omitempty" url:"long,omitempty"` + Double *float64 `json:"double,omitempty" url:"double,omitempty"` + Bool *bool `json:"bool,omitempty" url:"bool,omitempty"` + Datetime *time.Time `json:"datetime,omitempty" url:"datetime,omitempty"` + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + Uuid *uuid.UUID `json:"uuid,omitempty" url:"uuid,omitempty"` + Base64 *[]byte `json:"base64,omitempty" url:"base64,omitempty"` + List []string `json:"list,omitempty" url:"list,omitempty"` + Set []string `json:"set,omitempty" url:"set,omitempty"` + Map map[int]string `json:"map,omitempty" url:"map,omitempty"` +} + +func (o *ObjectWithOptionalField) UnmarshalJSON(data []byte) error { + type embed ObjectWithOptionalField + var unmarshaler = struct { + embed + Datetime *core.DateTime `json:"datetime,omitempty"` + Date *core.Date `json:"date,omitempty"` + }{ + embed: embed(*o), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *o = ObjectWithOptionalField(unmarshaler.embed) + o.Datetime = unmarshaler.Datetime.TimePtr() + o.Date = unmarshaler.Date.TimePtr() + return nil +} + +func (o *ObjectWithOptionalField) MarshalJSON() ([]byte, error) { + type embed ObjectWithOptionalField + var marshaler = struct { + embed + Datetime *core.DateTime `json:"datetime,omitempty"` + Date *core.Date `json:"date,omitempty"` + }{ + embed: embed(*o), + Datetime: core.NewOptionalDateTime(o.Datetime), + Date: core.NewOptionalDate(o.Date), + } + return json.Marshal(marshaler) +} + +func (o *ObjectWithOptionalField) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type ObjectWithRequiredField struct { + String string `json:"string" url:"string"` +} + +func (o *ObjectWithRequiredField) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type Animal struct { + Animal string + Dog *Dog + Cat *Cat +} + +func NewAnimalFromDog(value *Dog) *Animal { + return &Animal{Animal: "dog", Dog: value} +} + +func NewAnimalFromCat(value *Cat) *Animal { + return &Animal{Animal: "cat", Cat: value} +} + +func (a *Animal) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Animal string `json:"animal"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + a.Animal = unmarshaler.Animal + switch unmarshaler.Animal { + case "dog": + value := new(Dog) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + a.Dog = value + case "cat": + value := new(Cat) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + a.Cat = value + } + return nil +} + +func (a Animal) MarshalJSON() ([]byte, error) { + switch a.Animal { + default: + return nil, fmt.Errorf("invalid type %s in %T", a.Animal, a) + case "dog": + var marshaler = struct { + Animal string `json:"animal"` + *Dog + }{ + Animal: a.Animal, + Dog: a.Dog, + } + return json.Marshal(marshaler) + case "cat": + var marshaler = struct { + Animal string `json:"animal"` + *Cat + }{ + Animal: a.Animal, + Cat: a.Cat, + } + return json.Marshal(marshaler) + } +} + +type AnimalVisitor interface { + VisitDog(*Dog) error + VisitCat(*Cat) error +} + +func (a *Animal) Accept(visitor AnimalVisitor) error { + switch a.Animal { + default: + return fmt.Errorf("invalid type %s in %T", a.Animal, a) + case "dog": + return visitor.VisitDog(a.Dog) + case "cat": + return visitor.VisitCat(a.Cat) + } +} + +type Cat struct { + Name string `json:"name" url:"name"` + LikesToMeow bool `json:"likesToMeow" url:"likesToMeow"` +} + +func (c *Cat) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type Dog struct { + Name string `json:"name" url:"name"` + LikesToWoof bool `json:"likesToWoof" url:"likesToWoof"` +} + +func (d *Dog) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} diff --git a/seed/go-fiber/extends/.github/workflows/ci.yml b/seed/go-fiber/extends/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/extends/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/extends/core/stringer.go b/seed/go-fiber/extends/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/extends/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/extends/core/time.go b/seed/go-fiber/extends/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/extends/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/extends/go.mod b/seed/go-fiber/extends/go.mod new file mode 100644 index 00000000000..11b139bc1f2 --- /dev/null +++ b/seed/go-fiber/extends/go.mod @@ -0,0 +1,3 @@ +module github.com/extends/fern + +go 1.13 diff --git a/seed/go-fiber/extends/go.sum b/seed/go-fiber/extends/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/extends/snippet.json b/seed/go-fiber/extends/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/extends/types.go b/seed/go-fiber/extends/types.go new file mode 100644 index 00000000000..506b19176fb --- /dev/null +++ b/seed/go-fiber/extends/types.go @@ -0,0 +1,56 @@ +// This file was auto-generated by Fern from our API Definition. + +package extends + +import ( + fmt "fmt" + core "github.com/extends/fern/core" +) + +type Docs struct { + Docs string `json:"docs" url:"docs"` +} + +func (d *Docs) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type ExampleType struct { + Docs string `json:"docs" url:"docs"` + Name string `json:"name" url:"name"` +} + +func (e *ExampleType) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type Json struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` +} + +func (j *Json) String() string { + if value, err := core.StringifyJSON(j); err == nil { + return value + } + return fmt.Sprintf("%#v", j) +} + +type NestedType struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` + Name string `json:"name" url:"name"` +} + +func (n *NestedType) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} diff --git a/seed/go-fiber/file-download/.github/workflows/ci.yml b/seed/go-fiber/file-download/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/file-download/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/file-download/core/stringer.go b/seed/go-fiber/file-download/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/file-download/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/file-download/core/time.go b/seed/go-fiber/file-download/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/file-download/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/file-download/go.mod b/seed/go-fiber/file-download/go.mod new file mode 100644 index 00000000000..1893a4080ab --- /dev/null +++ b/seed/go-fiber/file-download/go.mod @@ -0,0 +1,3 @@ +module github.com/file-download/fern + +go 1.13 diff --git a/seed/go-fiber/file-download/go.sum b/seed/go-fiber/file-download/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/file-download/snippet.json b/seed/go-fiber/file-download/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/file-upload/.github/workflows/ci.yml b/seed/go-fiber/file-upload/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/file-upload/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/file-upload/core/stringer.go b/seed/go-fiber/file-upload/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/file-upload/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/file-upload/core/time.go b/seed/go-fiber/file-upload/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/file-upload/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/file-upload/go.mod b/seed/go-fiber/file-upload/go.mod new file mode 100644 index 00000000000..a64be340ba7 --- /dev/null +++ b/seed/go-fiber/file-upload/go.mod @@ -0,0 +1,3 @@ +module github.com/file-upload/fern + +go 1.13 diff --git a/seed/go-fiber/file-upload/go.sum b/seed/go-fiber/file-upload/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/file-upload/service.go b/seed/go-fiber/file-upload/service.go new file mode 100644 index 00000000000..440e2a29802 --- /dev/null +++ b/seed/go-fiber/file-upload/service.go @@ -0,0 +1,35 @@ +// This file was auto-generated by Fern from our API Definition. + +package fileupload + +import ( + fmt "fmt" + core "github.com/file-upload/fern/core" +) + +type JustFileWithQueryParamsRequet struct { + MaybeString *string `query:"maybeString"` + Integer int `query:"integer"` + MaybeInteger *int `query:"maybeInteger"` + ListOfStrings []string `query:"listOfStrings"` + OptionalListOfStrings []*string `query:"optionalListOfStrings"` +} + +type MyRequest struct { + MaybeString *string `json:"maybeString,omitempty" url:"maybeString,omitempty"` + Integer int `json:"integer" url:"integer"` + MaybeInteger *int `json:"maybeInteger,omitempty" url:"maybeInteger,omitempty"` + OptionalListOfStrings []string `json:"optionalListOfStrings,omitempty" url:"optionalListOfStrings,omitempty"` + ListOfObjects []*MyObject `json:"listOfObjects,omitempty" url:"listOfObjects,omitempty"` +} + +type MyObject struct { + Foo string `json:"foo" url:"foo"` +} + +func (m *MyObject) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-fiber/file-upload/snippet.json b/seed/go-fiber/file-upload/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/folders/.github/workflows/ci.yml b/seed/go-fiber/folders/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/folders/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/folders/a/d/types.go b/seed/go-fiber/folders/a/d/types.go new file mode 100644 index 00000000000..7a258fa374d --- /dev/null +++ b/seed/go-fiber/folders/a/d/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package d + +type Foo = string diff --git a/seed/go-fiber/folders/core/stringer.go b/seed/go-fiber/folders/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/folders/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/folders/core/time.go b/seed/go-fiber/folders/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/folders/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/folders/go.mod b/seed/go-fiber/folders/go.mod new file mode 100644 index 00000000000..35536dc07bc --- /dev/null +++ b/seed/go-fiber/folders/go.mod @@ -0,0 +1,3 @@ +module github.com/folders/fern + +go 1.13 diff --git a/seed/go-fiber/folders/go.sum b/seed/go-fiber/folders/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/folders/snippet.json b/seed/go-fiber/folders/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/idempotency-headers/.github/workflows/ci.yml b/seed/go-fiber/idempotency-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/idempotency-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/idempotency-headers/core/stringer.go b/seed/go-fiber/idempotency-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/idempotency-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/idempotency-headers/core/time.go b/seed/go-fiber/idempotency-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/idempotency-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/idempotency-headers/go.mod b/seed/go-fiber/idempotency-headers/go.mod new file mode 100644 index 00000000000..928f3bed6a1 --- /dev/null +++ b/seed/go-fiber/idempotency-headers/go.mod @@ -0,0 +1,3 @@ +module github.com/idempotency-headers/fern + +go 1.13 diff --git a/seed/go-fiber/idempotency-headers/go.sum b/seed/go-fiber/idempotency-headers/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/idempotency-headers/payment.go b/seed/go-fiber/idempotency-headers/payment.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/payment.go rename to seed/go-fiber/idempotency-headers/payment.go diff --git a/seed/go-fiber/idempotency-headers/snippet.json b/seed/go-fiber/idempotency-headers/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/imdb/.github/workflows/ci.yml b/seed/go-fiber/imdb/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/imdb/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/imdb/core/stringer.go b/seed/go-fiber/imdb/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/imdb/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/imdb/core/time.go b/seed/go-fiber/imdb/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/imdb/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/imdb/go.mod b/seed/go-fiber/imdb/go.mod new file mode 100644 index 00000000000..6a95cc5b0f1 --- /dev/null +++ b/seed/go-fiber/imdb/go.mod @@ -0,0 +1,3 @@ +module github.com/imdb/fern + +go 1.13 diff --git a/seed/go-fiber/imdb/go.sum b/seed/go-fiber/imdb/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/imdb/imdb.go b/seed/go-fiber/imdb/imdb.go new file mode 100644 index 00000000000..03b735fdf8b --- /dev/null +++ b/seed/go-fiber/imdb/imdb.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + fmt "fmt" + core "github.com/imdb/fern/core" +) + +type CreateMovieRequest struct { + Title string `json:"title" url:"title"` + Rating float64 `json:"rating" url:"rating"` +} + +func (c *CreateMovieRequest) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string diff --git a/seed/go-fiber/imdb/snippet.json b/seed/go-fiber/imdb/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/literal-headers/.github/workflows/ci.yml b/seed/go-fiber/literal-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/literal-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/literal-headers/core/stringer.go b/seed/go-fiber/literal-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/literal-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/literal-headers/core/time.go b/seed/go-fiber/literal-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/literal-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/literal-headers/go.mod b/seed/go-fiber/literal-headers/go.mod new file mode 100644 index 00000000000..2f5e49970b1 --- /dev/null +++ b/seed/go-fiber/literal-headers/go.mod @@ -0,0 +1,3 @@ +module github.com/literal-headers/fern + +go 1.13 diff --git a/seed/go-fiber/literal-headers/go.sum b/seed/go-fiber/literal-headers/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/literal-headers/snippet.json b/seed/go-fiber/literal-headers/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/literal-headers/with_non_literal_headers.go b/seed/go-fiber/literal-headers/with_non_literal_headers.go new file mode 100644 index 00000000000..b910d7bc92d --- /dev/null +++ b/seed/go-fiber/literal-headers/with_non_literal_headers.go @@ -0,0 +1,17 @@ +// This file was auto-generated by Fern from our API Definition. + +package literalheaders + +type WithNonLiteralHeadersRequest struct { + NonLiteralEndpointHeader string `header:"nonLiteralEndpointHeader"` + literalEndpointHeader string + trueEndpointHeader bool +} + +func (w *WithNonLiteralHeadersRequest) LiteralEndpointHeader() string { + return w.literalEndpointHeader +} + +func (w *WithNonLiteralHeadersRequest) TrueEndpointHeader() bool { + return w.trueEndpointHeader +} diff --git a/seed/go-fiber/literal/.github/workflows/ci.yml b/seed/go-fiber/literal/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/literal/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/literal/core/stringer.go b/seed/go-fiber/literal/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/literal/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/literal/core/time.go b/seed/go-fiber/literal/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/literal/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/literal/go.mod b/seed/go-fiber/literal/go.mod new file mode 100644 index 00000000000..3f1d855d469 --- /dev/null +++ b/seed/go-fiber/literal/go.mod @@ -0,0 +1,3 @@ +module github.com/literal/fern + +go 1.13 diff --git a/seed/go-fiber/literal/go.sum b/seed/go-fiber/literal/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/literal/headers.go b/seed/go-fiber/literal/headers.go new file mode 100644 index 00000000000..28d7855834a --- /dev/null +++ b/seed/go-fiber/literal/headers.go @@ -0,0 +1,47 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" +) + +type SendLiteralsInHeadersRequest struct { + Query string `json:"query" url:"query"` + endpointVersion string + async bool +} + +func (s *SendLiteralsInHeadersRequest) EndpointVersion() string { + return s.endpointVersion +} + +func (s *SendLiteralsInHeadersRequest) Async() bool { + return s.async +} + +func (s *SendLiteralsInHeadersRequest) UnmarshalJSON(data []byte) error { + type unmarshaler SendLiteralsInHeadersRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *s = SendLiteralsInHeadersRequest(body) + s.endpointVersion = "02-12-2024" + s.async = true + return nil +} + +func (s *SendLiteralsInHeadersRequest) MarshalJSON() ([]byte, error) { + type embed SendLiteralsInHeadersRequest + var marshaler = struct { + embed + EndpointVersion string `json:"X-Endpoint-Version"` + Async bool `json:"X-Async"` + }{ + embed: embed(*s), + EndpointVersion: "02-12-2024", + Async: true, + } + return json.Marshal(marshaler) +} diff --git a/seed/go-fiber/literal/inlined.go b/seed/go-fiber/literal/inlined.go new file mode 100644 index 00000000000..d81f4f0b357 --- /dev/null +++ b/seed/go-fiber/literal/inlined.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" +) + +type SendLiteralsInlinedRequest struct { + Query string `json:"query" url:"query"` + Temperature *float64 `json:"temperature,omitempty" url:"temperature,omitempty"` + prompt string + stream bool +} + +func (s *SendLiteralsInlinedRequest) Prompt() string { + return s.prompt +} + +func (s *SendLiteralsInlinedRequest) Stream() bool { + return s.stream +} + +func (s *SendLiteralsInlinedRequest) UnmarshalJSON(data []byte) error { + type unmarshaler SendLiteralsInlinedRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *s = SendLiteralsInlinedRequest(body) + s.prompt = "You are a helpful assistant" + s.stream = false + return nil +} + +func (s *SendLiteralsInlinedRequest) MarshalJSON() ([]byte, error) { + type embed SendLiteralsInlinedRequest + var marshaler = struct { + embed + Prompt string `json:"prompt"` + Stream bool `json:"stream"` + }{ + embed: embed(*s), + Prompt: "You are a helpful assistant", + Stream: false, + } + return json.Marshal(marshaler) +} diff --git a/seed/go-fiber/literal/query.go b/seed/go-fiber/literal/query.go new file mode 100644 index 00000000000..e1a523b1f7b --- /dev/null +++ b/seed/go-fiber/literal/query.go @@ -0,0 +1,17 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +type SendLiteralsInQueryRequest struct { + Query string `query:"query"` + prompt string + stream bool +} + +func (s *SendLiteralsInQueryRequest) Prompt() string { + return s.prompt +} + +func (s *SendLiteralsInQueryRequest) Stream() bool { + return s.stream +} diff --git a/seed/go-fiber/literal/reference.go b/seed/go-fiber/literal/reference.go new file mode 100644 index 00000000000..d3264eb0370 --- /dev/null +++ b/seed/go-fiber/literal/reference.go @@ -0,0 +1,60 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendRequest struct { + Query string `json:"query" url:"query"` + prompt string + stream bool +} + +func (s *SendRequest) Prompt() string { + return s.prompt +} + +func (s *SendRequest) Stream() bool { + return s.stream +} + +func (s *SendRequest) UnmarshalJSON(data []byte) error { + type embed SendRequest + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendRequest(unmarshaler.embed) + s.prompt = "You are a helpful assistant" + s.stream = false + return nil +} + +func (s *SendRequest) MarshalJSON() ([]byte, error) { + type embed SendRequest + var marshaler = struct { + embed + Prompt string `json:"prompt"` + Stream bool `json:"stream"` + }{ + embed: embed(*s), + Prompt: "You are a helpful assistant", + Stream: false, + } + return json.Marshal(marshaler) +} + +func (s *SendRequest) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-fiber/literal/snippet.json b/seed/go-fiber/literal/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/literal/types.go b/seed/go-fiber/literal/types.go new file mode 100644 index 00000000000..60870e635bd --- /dev/null +++ b/seed/go-fiber/literal/types.go @@ -0,0 +1,53 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendResponse struct { + Message string `json:"message" url:"message"` + Status int `json:"status" url:"status"` + success bool +} + +func (s *SendResponse) Success() bool { + return s.success +} + +func (s *SendResponse) UnmarshalJSON(data []byte) error { + type embed SendResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendResponse(unmarshaler.embed) + s.success = true + return nil +} + +func (s *SendResponse) MarshalJSON() ([]byte, error) { + type embed SendResponse + var marshaler = struct { + embed + Success bool `json:"success"` + }{ + embed: embed(*s), + Success: true, + } + return json.Marshal(marshaler) +} + +func (s *SendResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-fiber/multi-url-environment/.github/workflows/ci.yml b/seed/go-fiber/multi-url-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/multi-url-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/multi-url-environment/core/stringer.go b/seed/go-fiber/multi-url-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/multi-url-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/multi-url-environment/core/time.go b/seed/go-fiber/multi-url-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/multi-url-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/multi-url-environment/ec_2.go b/seed/go-fiber/multi-url-environment/ec_2.go new file mode 100644 index 00000000000..96e1778fd6b --- /dev/null +++ b/seed/go-fiber/multi-url-environment/ec_2.go @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment + +type BootInstanceRequest struct { + Size string `json:"size" url:"size"` +} diff --git a/seed/go-fiber/multi-url-environment/go.mod b/seed/go-fiber/multi-url-environment/go.mod new file mode 100644 index 00000000000..3959168c8db --- /dev/null +++ b/seed/go-fiber/multi-url-environment/go.mod @@ -0,0 +1,3 @@ +module github.com/multi-url-environment/fern + +go 1.13 diff --git a/seed/go-fiber/multi-url-environment/go.sum b/seed/go-fiber/multi-url-environment/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/multi-url-environment/s_3.go b/seed/go-fiber/multi-url-environment/s_3.go new file mode 100644 index 00000000000..906e165e9d2 --- /dev/null +++ b/seed/go-fiber/multi-url-environment/s_3.go @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment + +type GetPresignedUrlRequest struct { + S3Key string `json:"s3Key" url:"s3Key"` +} diff --git a/seed/go-fiber/multi-url-environment/snippet.json b/seed/go-fiber/multi-url-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/no-environment/.github/workflows/ci.yml b/seed/go-fiber/no-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/no-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/no-environment/core/stringer.go b/seed/go-fiber/no-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/no-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/no-environment/core/time.go b/seed/go-fiber/no-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/no-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/no-environment/go.mod b/seed/go-fiber/no-environment/go.mod new file mode 100644 index 00000000000..f98982ca0c7 --- /dev/null +++ b/seed/go-fiber/no-environment/go.mod @@ -0,0 +1,3 @@ +module github.com/no-environment/fern + +go 1.13 diff --git a/seed/go-fiber/no-environment/go.sum b/seed/go-fiber/no-environment/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/no-environment/snippet.json b/seed/go-fiber/no-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/object/.github/workflows/ci.yml b/seed/go-fiber/object/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/object/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/object/core/stringer.go b/seed/go-fiber/object/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/object/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/object/core/time.go b/seed/go-fiber/object/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/object/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/object/go.mod b/seed/go-fiber/object/go.mod new file mode 100644 index 00000000000..af8c8515ad7 --- /dev/null +++ b/seed/go-fiber/object/go.mod @@ -0,0 +1,5 @@ +module github.com/object/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-fiber/object/go.sum b/seed/go-fiber/object/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-fiber/object/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-fiber/object/snippet.json b/seed/go-fiber/object/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/object/types.go b/seed/go-fiber/object/types.go new file mode 100644 index 00000000000..18d4498179b --- /dev/null +++ b/seed/go-fiber/object/types.go @@ -0,0 +1,92 @@ +// This file was auto-generated by Fern from our API Definition. + +package object + +import ( + json "encoding/json" + fmt "fmt" + uuid "github.com/google/uuid" + core "github.com/object/fern/core" + time "time" +) + +type Name struct { + Id string `json:"id" url:"id"` + Value string `json:"value" url:"value"` +} + +func (n *Name) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +// Exercises all of the built-in types. +type Type struct { + One int `json:"one" url:"one"` + Two float64 `json:"two" url:"two"` + Three string `json:"three" url:"three"` + Four bool `json:"four" url:"four"` + Five int64 `json:"five" url:"five"` + Six time.Time `json:"six" url:"six"` + Seven time.Time `json:"seven" url:"seven" format:"date"` + Eight uuid.UUID `json:"eight" url:"eight"` + Nine []byte `json:"nine" url:"nine"` + Ten []int `json:"ten,omitempty" url:"ten,omitempty"` + Eleven []float64 `json:"eleven,omitempty" url:"eleven,omitempty"` + Twelve map[string]bool `json:"twelve,omitempty" url:"twelve,omitempty"` + Thirteen *int64 `json:"thirteen,omitempty" url:"thirteen,omitempty"` + Fourteen interface{} `json:"fourteen,omitempty" url:"fourteen,omitempty"` + Fifteen [][]int `json:"fifteen,omitempty" url:"fifteen,omitempty"` + Sixteen []map[string]int `json:"sixteen,omitempty" url:"sixteen,omitempty"` + Seventeen []*uuid.UUID `json:"seventeen,omitempty" url:"seventeen,omitempty"` + Nineteen *Name `json:"nineteen,omitempty" url:"nineteen,omitempty"` + eighteen string +} + +func (t *Type) Eighteen() string { + return t.eighteen +} + +func (t *Type) UnmarshalJSON(data []byte) error { + type embed Type + var unmarshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + }{ + embed: embed(*t), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *t = Type(unmarshaler.embed) + t.Six = unmarshaler.Six.Time() + t.Seven = unmarshaler.Seven.Time() + t.eighteen = "eighteen" + return nil +} + +func (t *Type) MarshalJSON() ([]byte, error) { + type embed Type + var marshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + Eighteen string `json:"eighteen"` + }{ + embed: embed(*t), + Six: core.NewDateTime(t.Six), + Seven: core.NewDate(t.Seven), + Eighteen: "eighteen", + } + return json.Marshal(marshaler) +} + +func (t *Type) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-fiber/objects-with-imports/.github/workflows/ci.yml b/seed/go-fiber/objects-with-imports/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/objects-with-imports/commons/types.go b/seed/go-fiber/objects-with-imports/commons/types.go new file mode 100644 index 00000000000..90bf2d4e873 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/commons/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + fmt "fmt" + core "github.com/objects-with-imports/fern/core" +) + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` +} + +func (m *Metadata) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-fiber/objects-with-imports/core/stringer.go b/seed/go-fiber/objects-with-imports/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/objects-with-imports/core/time.go b/seed/go-fiber/objects-with-imports/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/objects-with-imports/file/types.go b/seed/go-fiber/objects-with-imports/file/types.go new file mode 100644 index 00000000000..dd9224edba5 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/file/types.go @@ -0,0 +1,22 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +import ( + fmt "fmt" + fern "github.com/objects-with-imports/fern" + core "github.com/objects-with-imports/fern/core" +) + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*fern.File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` +} + +func (d *Directory) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} diff --git a/seed/go-fiber/objects-with-imports/go.mod b/seed/go-fiber/objects-with-imports/go.mod new file mode 100644 index 00000000000..bc1c954560c --- /dev/null +++ b/seed/go-fiber/objects-with-imports/go.mod @@ -0,0 +1,3 @@ +module github.com/objects-with-imports/fern + +go 1.13 diff --git a/seed/go-fiber/objects-with-imports/go.sum b/seed/go-fiber/objects-with-imports/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/objects-with-imports/snippet.json b/seed/go-fiber/objects-with-imports/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/objects-with-imports/types.go b/seed/go-fiber/objects-with-imports/types.go new file mode 100644 index 00000000000..971421132b2 --- /dev/null +++ b/seed/go-fiber/objects-with-imports/types.go @@ -0,0 +1,70 @@ +// This file was auto-generated by Fern from our API Definition. + +package objectswithimports + +import ( + fmt "fmt" + commons "github.com/objects-with-imports/fern/commons" + core "github.com/objects-with-imports/fern/core" +) + +type Node struct { + Id string `json:"id" url:"id"` + Label *string `json:"label,omitempty" url:"label,omitempty"` + Metadata *commons.Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` +} + +func (n *Node) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` +} + +func (t *Tree) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` + Info FileInfo `json:"info,omitempty" url:"info,omitempty"` +} + +func (f *File) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type FileInfo string + +const ( + // A regular file (e.g. foo.txt). + FileInfoRegular FileInfo = "REGULAR" + // A directory (e.g. foo/). + FileInfoDirectory FileInfo = "DIRECTORY" +) + +func NewFileInfoFromString(s string) (FileInfo, error) { + switch s { + case "REGULAR": + return FileInfoRegular, nil + case "DIRECTORY": + return FileInfoDirectory, nil + } + var t FileInfo + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (f FileInfo) Ptr() *FileInfo { + return &f +} diff --git a/seed/go-fiber/optional/.github/workflows/ci.yml b/seed/go-fiber/optional/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/optional/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/optional/core/stringer.go b/seed/go-fiber/optional/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/optional/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/optional/core/time.go b/seed/go-fiber/optional/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/optional/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/optional/go.mod b/seed/go-fiber/optional/go.mod new file mode 100644 index 00000000000..6e2c82f2fe2 --- /dev/null +++ b/seed/go-fiber/optional/go.mod @@ -0,0 +1,3 @@ +module github.com/optional/fern + +go 1.13 diff --git a/seed/go-fiber/optional/go.sum b/seed/go-fiber/optional/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/optional/snippet.json b/seed/go-fiber/optional/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/package-yml/.github/workflows/ci.yml b/seed/go-fiber/package-yml/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/package-yml/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/package-yml/core/stringer.go b/seed/go-fiber/package-yml/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/package-yml/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/package-yml/core/time.go b/seed/go-fiber/package-yml/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/package-yml/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/package-yml/go.mod b/seed/go-fiber/package-yml/go.mod new file mode 100644 index 00000000000..a48f613f354 --- /dev/null +++ b/seed/go-fiber/package-yml/go.mod @@ -0,0 +1,3 @@ +module github.com/package-yml/fern + +go 1.13 diff --git a/seed/go-fiber/package-yml/go.sum b/seed/go-fiber/package-yml/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/package-yml/snippet.json b/seed/go-fiber/package-yml/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/plain-text/.github/workflows/ci.yml b/seed/go-fiber/plain-text/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/plain-text/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/plain-text/core/stringer.go b/seed/go-fiber/plain-text/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/plain-text/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/plain-text/core/time.go b/seed/go-fiber/plain-text/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/plain-text/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/plain-text/go.mod b/seed/go-fiber/plain-text/go.mod new file mode 100644 index 00000000000..47b16a3473c --- /dev/null +++ b/seed/go-fiber/plain-text/go.mod @@ -0,0 +1,3 @@ +module github.com/plain-text/fern + +go 1.13 diff --git a/seed/go-fiber/plain-text/go.sum b/seed/go-fiber/plain-text/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/plain-text/snippet.json b/seed/go-fiber/plain-text/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/query-parameters/.github/workflows/ci.yml b/seed/go-fiber/query-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/query-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/query-parameters/core/stringer.go b/seed/go-fiber/query-parameters/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/query-parameters/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/query-parameters/core/time.go b/seed/go-fiber/query-parameters/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/query-parameters/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/query-parameters/go.mod b/seed/go-fiber/query-parameters/go.mod new file mode 100644 index 00000000000..e9d26fefebc --- /dev/null +++ b/seed/go-fiber/query-parameters/go.mod @@ -0,0 +1,5 @@ +module github.com/query-parameters/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-fiber/query-parameters/go.sum b/seed/go-fiber/query-parameters/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-fiber/query-parameters/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-fiber/query-parameters/snippet.json b/seed/go-fiber/query-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/query-parameters/user.go b/seed/go-fiber/query-parameters/user.go new file mode 100644 index 00000000000..b09646ac027 --- /dev/null +++ b/seed/go-fiber/query-parameters/user.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package queryparameters + +import ( + fmt "fmt" + uuid "github.com/google/uuid" + core "github.com/query-parameters/fern/core" + time "time" +) + +type GetUsersRequest struct { + Limit int `query:"limit"` + Id uuid.UUID `query:"id"` + Date time.Time `query:"date"` + Deadline time.Time `query:"deadline"` + Bytes []byte `query:"bytes"` + User *User `query:"user"` + KeyValue map[string]string `query:"keyValue"` + OptionalString *string `query:"optionalString"` + NestedUser *NestedUser `query:"nestedUser"` + ExcludeUser []*User `query:"excludeUser"` + Filter []string `query:"filter"` +} + +type NestedUser struct { + Name string `json:"name" url:"name"` + User *User `json:"user,omitempty" url:"user,omitempty"` +} + +func (n *NestedUser) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type User struct { + Name string `json:"name" url:"name"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` +} + +func (u *User) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-fiber/response-property/.github/workflows/ci.yml b/seed/go-fiber/response-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/response-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/response-property/core/stringer.go b/seed/go-fiber/response-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/response-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/response-property/core/time.go b/seed/go-fiber/response-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/response-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/response-property/go.mod b/seed/go-fiber/response-property/go.mod new file mode 100644 index 00000000000..a53aca15467 --- /dev/null +++ b/seed/go-fiber/response-property/go.mod @@ -0,0 +1,3 @@ +module github.com/response-property/fern + +go 1.13 diff --git a/seed/go-fiber/response-property/go.sum b/seed/go-fiber/response-property/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/response-property/service.go b/seed/go-fiber/response-property/service.go new file mode 100644 index 00000000000..e03f806dd21 --- /dev/null +++ b/seed/go-fiber/response-property/service.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package responseproperty + +import ( + fmt "fmt" + core "github.com/response-property/fern/core" +) + +type OptionalStringResponse = *StringResponse + +type StringResponse struct { + Data string `json:"data" url:"data"` +} + +func (s *StringResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type OptionalWithDocs = *WithDocs + +type Response struct { + Metadata map[string]string `json:"metadata,omitempty" url:"metadata,omitempty"` + Docs string `json:"docs" url:"docs"` + Data *Movie `json:"data,omitempty" url:"data,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} diff --git a/seed/go-fiber/response-property/snippet.json b/seed/go-fiber/response-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/response-property/types.go b/seed/go-fiber/response-property/types.go new file mode 100644 index 00000000000..4e75908efda --- /dev/null +++ b/seed/go-fiber/response-property/types.go @@ -0,0 +1,42 @@ +// This file was auto-generated by Fern from our API Definition. + +package responseproperty + +import ( + fmt "fmt" + core "github.com/response-property/fern/core" +) + +type WithMetadata struct { + Metadata map[string]string `json:"metadata,omitempty" url:"metadata,omitempty"` +} + +func (w *WithMetadata) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} + +type Movie struct { + Id string `json:"id" url:"id"` + Name string `json:"name" url:"name"` +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type WithDocs struct { + Docs string `json:"docs" url:"docs"` +} + +func (w *WithDocs) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} diff --git a/seed/go-fiber/seed.yml b/seed/go-fiber/seed.yml new file mode 100644 index 00000000000..1bfd02f9b01 --- /dev/null +++ b/seed/go-fiber/seed.yml @@ -0,0 +1,31 @@ +irVersion: v33 +docker: fernapi/fern-go-fiber:latest +dockerCommand: docker build -f ./generators/go/docker/Dockerfile.fiber -t fernapi/fern-go-fiber:latest ./generators/go +language: go +generatorType: sdk +defaultOutputMode: github +fixtures: + streaming: + - outputFolder: . + outputVersion: v2.0.0 + customConfig: + packageName: stream + module: + path: github.com/fern-api/stream-go + idempotency-headers: + - outputFolder: . + outputVersion: 0.0.1 + customConfig: + packageName: fern + module: + path: github.com/idempotency-headers/fern + includeLegacyClientOptions: true +scripts: + - docker: golang:1.18-alpine + commands: + - CGO_ENABLED=0 go test ./... +allowedFailures: + - exhaustive + - reserved-keywords + - trace + - websocket diff --git a/seed/go-fiber/single-url-environment-default/.github/workflows/ci.yml b/seed/go-fiber/single-url-environment-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/single-url-environment-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/single-url-environment-default/core/stringer.go b/seed/go-fiber/single-url-environment-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/single-url-environment-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/single-url-environment-default/core/time.go b/seed/go-fiber/single-url-environment-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/single-url-environment-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/single-url-environment-default/go.mod b/seed/go-fiber/single-url-environment-default/go.mod new file mode 100644 index 00000000000..b8e5300ef77 --- /dev/null +++ b/seed/go-fiber/single-url-environment-default/go.mod @@ -0,0 +1,3 @@ +module github.com/single-url-environment-default/fern + +go 1.13 diff --git a/seed/go-fiber/single-url-environment-default/go.sum b/seed/go-fiber/single-url-environment-default/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/single-url-environment-default/snippet.json b/seed/go-fiber/single-url-environment-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/single-url-environment-no-default/.github/workflows/ci.yml b/seed/go-fiber/single-url-environment-no-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/single-url-environment-no-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/single-url-environment-no-default/core/stringer.go b/seed/go-fiber/single-url-environment-no-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/single-url-environment-no-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/single-url-environment-no-default/core/time.go b/seed/go-fiber/single-url-environment-no-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/single-url-environment-no-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/single-url-environment-no-default/go.mod b/seed/go-fiber/single-url-environment-no-default/go.mod new file mode 100644 index 00000000000..35b4fb9c8f8 --- /dev/null +++ b/seed/go-fiber/single-url-environment-no-default/go.mod @@ -0,0 +1,3 @@ +module github.com/single-url-environment-no-default/fern + +go 1.13 diff --git a/seed/go-fiber/single-url-environment-no-default/go.sum b/seed/go-fiber/single-url-environment-no-default/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/single-url-environment-no-default/snippet.json b/seed/go-fiber/single-url-environment-no-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/streaming/.github/workflows/ci.yml b/seed/go-fiber/streaming/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/streaming/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/streaming/core/stringer.go b/seed/go-fiber/streaming/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/streaming/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/streaming/core/time.go b/seed/go-fiber/streaming/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/streaming/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/streaming/dummy.go b/seed/go-fiber/streaming/dummy.go new file mode 100644 index 00000000000..b6778e3bb4d --- /dev/null +++ b/seed/go-fiber/streaming/dummy.go @@ -0,0 +1,24 @@ +// This file was auto-generated by Fern from our API Definition. + +package stream + +import ( + fmt "fmt" + core "github.com/fern-api/stream-go/v2/core" +) + +type GenerateStreamRequestzs struct { + NumEvents int `json:"num_events" url:"num_events"` +} + +type StreamResponse struct { + Id string `json:"id" url:"id"` + Name *string `json:"name,omitempty" url:"name,omitempty"` +} + +func (s *StreamResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-fiber/streaming/go.mod b/seed/go-fiber/streaming/go.mod new file mode 100644 index 00000000000..473c2cf54a2 --- /dev/null +++ b/seed/go-fiber/streaming/go.mod @@ -0,0 +1,3 @@ +module github.com/fern-api/stream-go/v2 + +go 1.18 diff --git a/seed/go-fiber/streaming/snippet.json b/seed/go-fiber/streaming/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/undiscriminated-unions/.github/workflows/ci.yml b/seed/go-fiber/undiscriminated-unions/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/undiscriminated-unions/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/undiscriminated-unions/core/stringer.go b/seed/go-fiber/undiscriminated-unions/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/undiscriminated-unions/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/undiscriminated-unions/core/time.go b/seed/go-fiber/undiscriminated-unions/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/undiscriminated-unions/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/undiscriminated-unions/go.mod b/seed/go-fiber/undiscriminated-unions/go.mod new file mode 100644 index 00000000000..a45336a7da2 --- /dev/null +++ b/seed/go-fiber/undiscriminated-unions/go.mod @@ -0,0 +1,3 @@ +module github.com/undiscriminated-unions/fern + +go 1.13 diff --git a/seed/go-fiber/undiscriminated-unions/go.sum b/seed/go-fiber/undiscriminated-unions/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/undiscriminated-unions/snippet.json b/seed/go-fiber/undiscriminated-unions/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/undiscriminated-unions/union.go b/seed/go-fiber/undiscriminated-unions/union.go new file mode 100644 index 00000000000..126f606cab3 --- /dev/null +++ b/seed/go-fiber/undiscriminated-unions/union.go @@ -0,0 +1,114 @@ +// This file was auto-generated by Fern from our API Definition. + +package undiscriminatedunions + +import ( + json "encoding/json" + fmt "fmt" +) + +// Several different types are accepted. +type MyUnion struct { + typeName string + String string + StringList []string + Integer int + IntegerList []int + IntegerListList [][]int +} + +func NewMyUnionFromString(value string) *MyUnion { + return &MyUnion{typeName: "string", String: value} +} + +func NewMyUnionFromStringList(value []string) *MyUnion { + return &MyUnion{typeName: "stringList", StringList: value} +} + +func NewMyUnionFromInteger(value int) *MyUnion { + return &MyUnion{typeName: "integer", Integer: value} +} + +func NewMyUnionFromIntegerList(value []int) *MyUnion { + return &MyUnion{typeName: "integerList", IntegerList: value} +} + +func NewMyUnionFromIntegerListList(value [][]int) *MyUnion { + return &MyUnion{typeName: "integerListList", IntegerListList: value} +} + +func (m *MyUnion) UnmarshalJSON(data []byte) error { + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.typeName = "string" + m.String = valueString + return nil + } + var valueStringList []string + if err := json.Unmarshal(data, &valueStringList); err == nil { + m.typeName = "stringList" + m.StringList = valueStringList + return nil + } + var valueInteger int + if err := json.Unmarshal(data, &valueInteger); err == nil { + m.typeName = "integer" + m.Integer = valueInteger + return nil + } + var valueIntegerList []int + if err := json.Unmarshal(data, &valueIntegerList); err == nil { + m.typeName = "integerList" + m.IntegerList = valueIntegerList + return nil + } + var valueIntegerListList [][]int + if err := json.Unmarshal(data, &valueIntegerListList); err == nil { + m.typeName = "integerListList" + m.IntegerListList = valueIntegerListList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MyUnion) MarshalJSON() ([]byte, error) { + switch m.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return json.Marshal(m.String) + case "stringList": + return json.Marshal(m.StringList) + case "integer": + return json.Marshal(m.Integer) + case "integerList": + return json.Marshal(m.IntegerList) + case "integerListList": + return json.Marshal(m.IntegerListList) + } +} + +type MyUnionVisitor interface { + VisitString(string) error + VisitStringList([]string) error + VisitInteger(int) error + VisitIntegerList([]int) error + VisitIntegerListList([][]int) error +} + +func (m *MyUnion) Accept(visitor MyUnionVisitor) error { + switch m.typeName { + default: + return fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return visitor.VisitString(m.String) + case "stringList": + return visitor.VisitStringList(m.StringList) + case "integer": + return visitor.VisitInteger(m.Integer) + case "integerList": + return visitor.VisitIntegerList(m.IntegerList) + case "integerListList": + return visitor.VisitIntegerListList(m.IntegerListList) + } +} diff --git a/seed/go-fiber/unknown/.github/workflows/ci.yml b/seed/go-fiber/unknown/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/unknown/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/unknown/core/stringer.go b/seed/go-fiber/unknown/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/unknown/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/unknown/core/time.go b/seed/go-fiber/unknown/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/unknown/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/unknown/go.mod b/seed/go-fiber/unknown/go.mod new file mode 100644 index 00000000000..c16b006703e --- /dev/null +++ b/seed/go-fiber/unknown/go.mod @@ -0,0 +1,3 @@ +module github.com/unknown/fern + +go 1.13 diff --git a/seed/go-fiber/unknown/go.sum b/seed/go-fiber/unknown/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/unknown/snippet.json b/seed/go-fiber/unknown/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/unknown/types.go b/seed/go-fiber/unknown/types.go new file mode 100644 index 00000000000..0600ff3a611 --- /dev/null +++ b/seed/go-fiber/unknown/types.go @@ -0,0 +1,21 @@ +// This file was auto-generated by Fern from our API Definition. + +package unknownasany + +import ( + fmt "fmt" + core "github.com/unknown/fern/core" +) + +type MyAlias = interface{} + +type MyObject struct { + Unknown interface{} `json:"unknown,omitempty" url:"unknown,omitempty"` +} + +func (m *MyObject) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-fiber/variables/.github/workflows/ci.yml b/seed/go-fiber/variables/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/variables/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/variables/core/stringer.go b/seed/go-fiber/variables/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/variables/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/variables/core/time.go b/seed/go-fiber/variables/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/variables/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/variables/go.mod b/seed/go-fiber/variables/go.mod new file mode 100644 index 00000000000..46aea28b366 --- /dev/null +++ b/seed/go-fiber/variables/go.mod @@ -0,0 +1,3 @@ +module github.com/variables/fern + +go 1.13 diff --git a/seed/go-fiber/variables/go.sum b/seed/go-fiber/variables/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/variables/snippet.json b/seed/go-fiber/variables/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/alias/.github/workflows/ci.yml b/seed/go-model/alias/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/alias/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/alias/core/stringer.go b/seed/go-model/alias/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/alias/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/alias/core/time.go b/seed/go-model/alias/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/alias/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/alias/go.mod b/seed/go-model/alias/go.mod new file mode 100644 index 00000000000..677c30f3e8a --- /dev/null +++ b/seed/go-model/alias/go.mod @@ -0,0 +1,3 @@ +module github.com/alias/fern + +go 1.13 diff --git a/seed/go-model/alias/go.sum b/seed/go-model/alias/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/alias/snippet.json b/seed/go-model/alias/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/alias/types.go b/seed/go-model/alias/types.go new file mode 100644 index 00000000000..4289257179d --- /dev/null +++ b/seed/go-model/alias/types.go @@ -0,0 +1,27 @@ +// This file was auto-generated by Fern from our API Definition. + +package alias + +import ( + fmt "fmt" + core "github.com/alias/fern/core" +) + +// Object is an alias for a type. +type Object = *Type + +// A simple type with just a name. +type Type struct { + Id TypeId `json:"id" url:"id"` + Name string `json:"name" url:"name"` +} + +func (t *Type) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +// An alias for type IDs. +type TypeId = string diff --git a/seed/go-model/api-wide-base-path/.github/workflows/ci.yml b/seed/go-model/api-wide-base-path/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/api-wide-base-path/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/api-wide-base-path/core/stringer.go b/seed/go-model/api-wide-base-path/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/api-wide-base-path/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/api-wide-base-path/core/time.go b/seed/go-model/api-wide-base-path/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/api-wide-base-path/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/api-wide-base-path/go.mod b/seed/go-model/api-wide-base-path/go.mod new file mode 100644 index 00000000000..a1df980a794 --- /dev/null +++ b/seed/go-model/api-wide-base-path/go.mod @@ -0,0 +1,3 @@ +module github.com/api-wide-base-path/fern + +go 1.13 diff --git a/seed/go-model/api-wide-base-path/go.sum b/seed/go-model/api-wide-base-path/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/api-wide-base-path/snippet.json b/seed/go-model/api-wide-base-path/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/audiences/.github/workflows/ci.yml b/seed/go-model/audiences/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/audiences/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/audiences/core/stringer.go b/seed/go-model/audiences/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/audiences/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/audiences/core/time.go b/seed/go-model/audiences/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/audiences/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/audiences/foldera/service.go b/seed/go-model/audiences/foldera/service.go new file mode 100644 index 00000000000..8b478e219ac --- /dev/null +++ b/seed/go-model/audiences/foldera/service.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package foldera + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + folderb "github.com/audiences/fern/folderb" +) + +type Response struct { + Foo *folderb.Foo `json:"foo,omitempty" url:"foo,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} diff --git a/seed/go-model/audiences/folderb/types.go b/seed/go-model/audiences/folderb/types.go new file mode 100644 index 00000000000..30966723530 --- /dev/null +++ b/seed/go-model/audiences/folderb/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderb + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + folderc "github.com/audiences/fern/folderc" +) + +type Foo struct { + Foo *folderc.Foo `json:"foo,omitempty" url:"foo,omitempty"` +} + +func (f *Foo) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-model/audiences/folderc/types.go b/seed/go-model/audiences/folderc/types.go new file mode 100644 index 00000000000..9a418f1b747 --- /dev/null +++ b/seed/go-model/audiences/folderc/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderc + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" + uuid "github.com/google/uuid" +) + +type Foo struct { + BarProperty uuid.UUID `json:"bar_property" url:"bar_property"` +} + +func (f *Foo) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-model/audiences/foo.go b/seed/go-model/audiences/foo.go new file mode 100644 index 00000000000..4f920ac3ad1 --- /dev/null +++ b/seed/go-model/audiences/foo.go @@ -0,0 +1,21 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type ImportingType struct { + Imported Imported `json:"imported" url:"imported"` +} + +func (i *ImportingType) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type OptionalString = *string diff --git a/seed/go-model/audiences/go.mod b/seed/go-model/audiences/go.mod new file mode 100644 index 00000000000..be4474586bd --- /dev/null +++ b/seed/go-model/audiences/go.mod @@ -0,0 +1,5 @@ +module github.com/audiences/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-model/audiences/go.sum b/seed/go-model/audiences/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-model/audiences/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-model/audiences/snippet.json b/seed/go-model/audiences/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/audiences/types.go b/seed/go-model/audiences/types.go new file mode 100644 index 00000000000..c6cb753155c --- /dev/null +++ b/seed/go-model/audiences/types.go @@ -0,0 +1,22 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type Imported = string + +type FilteredType struct { + PublicProperty *string `json:"public_property,omitempty" url:"public_property,omitempty"` + PrivateProperty int `json:"private_property" url:"private_property"` +} + +func (f *FilteredType) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-model/auth-environment-variables/.github/workflows/ci.yml b/seed/go-model/auth-environment-variables/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/auth-environment-variables/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/auth-environment-variables/core/stringer.go b/seed/go-model/auth-environment-variables/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/auth-environment-variables/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/auth-environment-variables/core/time.go b/seed/go-model/auth-environment-variables/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/auth-environment-variables/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/auth-environment-variables/go.mod b/seed/go-model/auth-environment-variables/go.mod new file mode 100644 index 00000000000..a06655610bf --- /dev/null +++ b/seed/go-model/auth-environment-variables/go.mod @@ -0,0 +1,3 @@ +module github.com/auth-environment-variables/fern + +go 1.13 diff --git a/seed/go-model/auth-environment-variables/go.sum b/seed/go-model/auth-environment-variables/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/auth-environment-variables/snippet.json b/seed/go-model/auth-environment-variables/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/basic-auth/.github/workflows/ci.yml b/seed/go-model/basic-auth/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/basic-auth/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/basic-auth/core/stringer.go b/seed/go-model/basic-auth/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/basic-auth/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/basic-auth/core/time.go b/seed/go-model/basic-auth/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/basic-auth/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/basic-auth/go.mod b/seed/go-model/basic-auth/go.mod new file mode 100644 index 00000000000..91034c571ad --- /dev/null +++ b/seed/go-model/basic-auth/go.mod @@ -0,0 +1,3 @@ +module github.com/basic-auth/fern + +go 1.13 diff --git a/seed/go-model/basic-auth/go.sum b/seed/go-model/basic-auth/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/basic-auth/snippet.json b/seed/go-model/basic-auth/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/basic-auth/types.go b/seed/go-model/basic-auth/types.go new file mode 100644 index 00000000000..b1af9f28cad --- /dev/null +++ b/seed/go-model/basic-auth/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package basicauth + +import ( + fmt "fmt" + core "github.com/basic-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/bearer-token-environment-variable/.github/workflows/ci.yml b/seed/go-model/bearer-token-environment-variable/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/bearer-token-environment-variable/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/bearer-token-environment-variable/core/stringer.go b/seed/go-model/bearer-token-environment-variable/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/bearer-token-environment-variable/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/bearer-token-environment-variable/core/time.go b/seed/go-model/bearer-token-environment-variable/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/bearer-token-environment-variable/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/bearer-token-environment-variable/go.mod b/seed/go-model/bearer-token-environment-variable/go.mod new file mode 100644 index 00000000000..cf3bd5f35fb --- /dev/null +++ b/seed/go-model/bearer-token-environment-variable/go.mod @@ -0,0 +1,3 @@ +module github.com/bearer-token-environment-variable/fern + +go 1.13 diff --git a/seed/go-model/bearer-token-environment-variable/go.sum b/seed/go-model/bearer-token-environment-variable/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/bearer-token-environment-variable/snippet.json b/seed/go-model/bearer-token-environment-variable/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/bytes/.github/workflows/ci.yml b/seed/go-model/bytes/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/bytes/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/bytes/core/stringer.go b/seed/go-model/bytes/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/bytes/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/bytes/core/time.go b/seed/go-model/bytes/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/bytes/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/bytes/go.mod b/seed/go-model/bytes/go.mod new file mode 100644 index 00000000000..eaa9f6476d5 --- /dev/null +++ b/seed/go-model/bytes/go.mod @@ -0,0 +1,3 @@ +module github.com/bytes/fern + +go 1.13 diff --git a/seed/go-model/bytes/go.sum b/seed/go-model/bytes/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/bytes/snippet.json b/seed/go-model/bytes/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/circular-references/.github/workflows/ci.yml b/seed/go-model/circular-references/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/circular-references/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/circular-references/core/stringer.go b/seed/go-model/circular-references/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/circular-references/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/circular-references/core/time.go b/seed/go-model/circular-references/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/circular-references/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/circular-references/go.mod b/seed/go-model/circular-references/go.mod new file mode 100644 index 00000000000..acd7c9ec857 --- /dev/null +++ b/seed/go-model/circular-references/go.mod @@ -0,0 +1,3 @@ +module github.com/circular-references/fern + +go 1.13 diff --git a/seed/go-model/circular-references/go.sum b/seed/go-model/circular-references/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/circular-references/snippet.json b/seed/go-model/circular-references/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/circular-references/types.go b/seed/go-model/circular-references/types.go new file mode 100644 index 00000000000..63ef560c114 --- /dev/null +++ b/seed/go-model/circular-references/types.go @@ -0,0 +1,265 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/circular-references/fern/core" +) + +type ImportingA struct { + A *A `json:"a,omitempty" url:"a,omitempty"` +} + +func (i *ImportingA) String() string { + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type RootType struct { + S string `json:"s" url:"s"` +} + +func (r *RootType) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type A struct { + S string `json:"s" url:"s"` +} + +func (a *A) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type ContainerValue struct { + Type string + List []*FieldValue + Optional *FieldValue +} + +func NewContainerValueFromList(value []*FieldValue) *ContainerValue { + return &ContainerValue{Type: "list", List: value} +} + +func NewContainerValueFromOptional(value *FieldValue) *ContainerValue { + return &ContainerValue{Type: "optional", Optional: value} +} + +func (c *ContainerValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + c.Type = unmarshaler.Type + switch unmarshaler.Type { + case "list": + var valueUnmarshaler struct { + List []*FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.List = valueUnmarshaler.List + case "optional": + var valueUnmarshaler struct { + Optional *FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.Optional = valueUnmarshaler.Optional + } + return nil +} + +func (c ContainerValue) MarshalJSON() ([]byte, error) { + switch c.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + var marshaler = struct { + Type string `json:"type"` + List []*FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + List: c.List, + } + return json.Marshal(marshaler) + case "optional": + var marshaler = struct { + Type string `json:"type"` + Optional *FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + Optional: c.Optional, + } + return json.Marshal(marshaler) + } +} + +type ContainerValueVisitor interface { + VisitList([]*FieldValue) error + VisitOptional(*FieldValue) error +} + +func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { + switch c.Type { + default: + return fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + return visitor.VisitList(c.List) + case "optional": + return visitor.VisitOptional(c.Optional) + } +} + +type FieldValue struct { + Type string + PrimitiveValue PrimitiveValue + ObjectValue *ObjectValue + ContainerValue *ContainerValue +} + +func NewFieldValueFromPrimitiveValue(value PrimitiveValue) *FieldValue { + return &FieldValue{Type: "primitive_value", PrimitiveValue: value} +} + +func NewFieldValueFromObjectValue(value *ObjectValue) *FieldValue { + return &FieldValue{Type: "object_value", ObjectValue: value} +} + +func NewFieldValueFromContainerValue(value *ContainerValue) *FieldValue { + return &FieldValue{Type: "container_value", ContainerValue: value} +} + +func (f *FieldValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + f.Type = unmarshaler.Type + switch unmarshaler.Type { + case "primitive_value": + var valueUnmarshaler struct { + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.PrimitiveValue = valueUnmarshaler.PrimitiveValue + case "object_value": + value := new(ObjectValue) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + f.ObjectValue = value + case "container_value": + var valueUnmarshaler struct { + ContainerValue *ContainerValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.ContainerValue = valueUnmarshaler.ContainerValue + } + return nil +} + +func (f FieldValue) MarshalJSON() ([]byte, error) { + switch f.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + var marshaler = struct { + Type string `json:"type"` + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + }{ + Type: f.Type, + PrimitiveValue: f.PrimitiveValue, + } + return json.Marshal(marshaler) + case "object_value": + var marshaler = struct { + Type string `json:"type"` + *ObjectValue + }{ + Type: f.Type, + ObjectValue: f.ObjectValue, + } + return json.Marshal(marshaler) + case "container_value": + var marshaler = struct { + Type string `json:"type"` + ContainerValue *ContainerValue `json:"value,omitempty"` + }{ + Type: f.Type, + ContainerValue: f.ContainerValue, + } + return json.Marshal(marshaler) + } +} + +type FieldValueVisitor interface { + VisitPrimitiveValue(PrimitiveValue) error + VisitObjectValue(*ObjectValue) error + VisitContainerValue(*ContainerValue) error +} + +func (f *FieldValue) Accept(visitor FieldValueVisitor) error { + switch f.Type { + default: + return fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + return visitor.VisitPrimitiveValue(f.PrimitiveValue) + case "object_value": + return visitor.VisitObjectValue(f.ObjectValue) + case "container_value": + return visitor.VisitContainerValue(f.ContainerValue) + } +} + +type ObjectValue struct { +} + +func (o *ObjectValue) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type PrimitiveValue string + +const ( + PrimitiveValueString PrimitiveValue = "STRING" + PrimitiveValueNumber PrimitiveValue = "NUMBER" +) + +func NewPrimitiveValueFromString(s string) (PrimitiveValue, error) { + switch s { + case "STRING": + return PrimitiveValueString, nil + case "NUMBER": + return PrimitiveValueNumber, nil + } + var t PrimitiveValue + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (p PrimitiveValue) Ptr() *PrimitiveValue { + return &p +} diff --git a/seed/go-model/custom-auth/.github/workflows/ci.yml b/seed/go-model/custom-auth/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/custom-auth/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/custom-auth/core/stringer.go b/seed/go-model/custom-auth/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/custom-auth/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/custom-auth/core/time.go b/seed/go-model/custom-auth/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/custom-auth/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/custom-auth/go.mod b/seed/go-model/custom-auth/go.mod new file mode 100644 index 00000000000..513db0ccbe2 --- /dev/null +++ b/seed/go-model/custom-auth/go.mod @@ -0,0 +1,3 @@ +module github.com/custom-auth/fern + +go 1.13 diff --git a/seed/go-model/custom-auth/go.sum b/seed/go-model/custom-auth/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/custom-auth/snippet.json b/seed/go-model/custom-auth/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/custom-auth/types.go b/seed/go-model/custom-auth/types.go new file mode 100644 index 00000000000..c5866144f74 --- /dev/null +++ b/seed/go-model/custom-auth/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package customauth + +import ( + fmt "fmt" + core "github.com/custom-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/enum/.github/workflows/ci.yml b/seed/go-model/enum/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/enum/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/enum/core/stringer.go b/seed/go-model/enum/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/enum/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/enum/core/time.go b/seed/go-model/enum/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/enum/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/enum/go.mod b/seed/go-model/enum/go.mod new file mode 100644 index 00000000000..f3d0e706536 --- /dev/null +++ b/seed/go-model/enum/go.mod @@ -0,0 +1,3 @@ +module github.com/enum/fern + +go 1.13 diff --git a/seed/go-model/enum/go.sum b/seed/go-model/enum/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/enum/inlined_request.go b/seed/go-model/enum/inlined_request.go new file mode 100644 index 00000000000..bcbce5fdcf3 --- /dev/null +++ b/seed/go-model/enum/inlined_request.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum diff --git a/seed/go-model/enum/query_param.go b/seed/go-model/enum/query_param.go new file mode 100644 index 00000000000..bcbce5fdcf3 --- /dev/null +++ b/seed/go-model/enum/query_param.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum diff --git a/seed/go-model/enum/snippet.json b/seed/go-model/enum/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/enum/types.go b/seed/go-model/enum/types.go new file mode 100644 index 00000000000..68bc8963a63 --- /dev/null +++ b/seed/go-model/enum/types.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum + +import ( + fmt "fmt" +) + +// Tests enum name and value can be +// different. +type Operand string + +const ( + OperandGreaterThan Operand = ">" + OperandEqualTo Operand = "=" + // The name and value should be similar + // are similar for less than. + OperandLessThan Operand = "less_than" +) + +func NewOperandFromString(s string) (Operand, error) { + switch s { + case ">": + return OperandGreaterThan, nil + case "=": + return OperandEqualTo, nil + case "less_than": + return OperandLessThan, nil + } + var t Operand + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (o Operand) Ptr() *Operand { + return &o +} diff --git a/seed/go-model/error-property/.github/workflows/ci.yml b/seed/go-model/error-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/error-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/error-property/core/stringer.go b/seed/go-model/error-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/error-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/error-property/core/time.go b/seed/go-model/error-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/error-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/error-property/go.mod b/seed/go-model/error-property/go.mod new file mode 100644 index 00000000000..f48cabcb94b --- /dev/null +++ b/seed/go-model/error-property/go.mod @@ -0,0 +1,3 @@ +module github.com/error-property/fern + +go 1.13 diff --git a/seed/go-model/error-property/go.sum b/seed/go-model/error-property/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/error-property/snippet.json b/seed/go-model/error-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/error-property/types.go b/seed/go-model/error-property/types.go new file mode 100644 index 00000000000..518904ff7de --- /dev/null +++ b/seed/go-model/error-property/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package errorproperty + +import ( + fmt "fmt" + core "github.com/error-property/fern/core" +) + +type PropertyBasedErrorTestBody struct { + Message string `json:"message" url:"message"` +} + +func (p *PropertyBasedErrorTestBody) String() string { + if value, err := core.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} diff --git a/seed/go-model/examples/.github/workflows/ci.yml b/seed/go-model/examples/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/examples/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/examples/commons/types.go b/seed/go-model/examples/commons/types.go new file mode 100644 index 00000000000..14cad3066ac --- /dev/null +++ b/seed/go-model/examples/commons/types.go @@ -0,0 +1,190 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/examples/fern/core" +) + +type Data struct { + Type string + String string + Base64 []byte +} + +func NewDataFromString(value string) *Data { + return &Data{Type: "string", String: value} +} + +func NewDataFromBase64(value []byte) *Data { + return &Data{Type: "base64", Base64: value} +} + +func (d *Data) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + d.Type = unmarshaler.Type + switch unmarshaler.Type { + case "string": + var valueUnmarshaler struct { + String string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.String = valueUnmarshaler.String + case "base64": + var valueUnmarshaler struct { + Base64 []byte `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.Base64 = valueUnmarshaler.Base64 + } + return nil +} + +func (d Data) MarshalJSON() ([]byte, error) { + switch d.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + var marshaler = struct { + Type string `json:"type"` + String string `json:"value"` + }{ + Type: d.Type, + String: d.String, + } + return json.Marshal(marshaler) + case "base64": + var marshaler = struct { + Type string `json:"type"` + Base64 []byte `json:"value"` + }{ + Type: d.Type, + Base64: d.Base64, + } + return json.Marshal(marshaler) + } +} + +type DataVisitor interface { + VisitString(string) error + VisitBase64([]byte) error +} + +func (d *Data) Accept(visitor DataVisitor) error { + switch d.Type { + default: + return fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + return visitor.VisitString(d.String) + case "base64": + return visitor.VisitBase64(d.Base64) + } +} + +type EventInfo struct { + Type string + Metadata *Metadata + Tag Tag +} + +func NewEventInfoFromMetadata(value *Metadata) *EventInfo { + return &EventInfo{Type: "metadata", Metadata: value} +} + +func NewEventInfoFromTag(value Tag) *EventInfo { + return &EventInfo{Type: "tag", Tag: value} +} + +func (e *EventInfo) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "metadata": + value := new(Metadata) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Metadata = value + case "tag": + var valueUnmarshaler struct { + Tag Tag `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + e.Tag = valueUnmarshaler.Tag + } + return nil +} + +func (e EventInfo) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + var marshaler = struct { + Type string `json:"type"` + *Metadata + }{ + Type: e.Type, + Metadata: e.Metadata, + } + return json.Marshal(marshaler) + case "tag": + var marshaler = struct { + Type string `json:"type"` + Tag Tag `json:"value"` + }{ + Type: e.Type, + Tag: e.Tag, + } + return json.Marshal(marshaler) + } +} + +type EventInfoVisitor interface { + VisitMetadata(*Metadata) error + VisitTag(Tag) error +} + +func (e *EventInfo) Accept(visitor EventInfoVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + return visitor.VisitMetadata(e.Metadata) + case "tag": + return visitor.VisitTag(e.Tag) + } +} + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` + JsonString *string `json:"jsonString,omitempty" url:"jsonString,omitempty"` +} + +func (m *Metadata) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Tag = string diff --git a/seed/go-model/examples/core/stringer.go b/seed/go-model/examples/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/examples/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/examples/core/time.go b/seed/go-model/examples/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/examples/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/examples/file/service.go b/seed/go-model/examples/file/service.go new file mode 100644 index 00000000000..ea34b32bdeb --- /dev/null +++ b/seed/go-model/examples/file/service.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package file diff --git a/seed/go-model/examples/file/types.go b/seed/go-model/examples/file/types.go new file mode 100644 index 00000000000..edc509f893e --- /dev/null +++ b/seed/go-model/examples/file/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type Filename = string diff --git a/seed/go-model/examples/go.mod b/seed/go-model/examples/go.mod new file mode 100644 index 00000000000..aa33f862049 --- /dev/null +++ b/seed/go-model/examples/go.mod @@ -0,0 +1,5 @@ +module github.com/examples/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-model/examples/go.sum b/seed/go-model/examples/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-model/examples/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-model/examples/service.go b/seed/go-model/examples/service.go new file mode 100644 index 00000000000..50638e02600 --- /dev/null +++ b/seed/go-model/examples/service.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples diff --git a/seed/go-model/examples/snippet.json b/seed/go-model/examples/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/examples/types.go b/seed/go-model/examples/types.go new file mode 100644 index 00000000000..234f4daf1bf --- /dev/null +++ b/seed/go-model/examples/types.go @@ -0,0 +1,652 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + fmt "fmt" + commons "github.com/examples/fern/commons" + core "github.com/examples/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type Actor struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` +} + +func (a *Actor) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type Actress struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` +} + +func (a *Actress) String() string { + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type CastMember struct { + typeName string + Actor *Actor + Actress *Actress + StuntDouble *StuntDouble +} + +func NewCastMemberFromActor(value *Actor) *CastMember { + return &CastMember{typeName: "actor", Actor: value} +} + +func NewCastMemberFromActress(value *Actress) *CastMember { + return &CastMember{typeName: "actress", Actress: value} +} + +func NewCastMemberFromStuntDouble(value *StuntDouble) *CastMember { + return &CastMember{typeName: "stuntDouble", StuntDouble: value} +} + +func (c *CastMember) UnmarshalJSON(data []byte) error { + valueActor := new(Actor) + if err := json.Unmarshal(data, &valueActor); err == nil { + c.typeName = "actor" + c.Actor = valueActor + return nil + } + valueActress := new(Actress) + if err := json.Unmarshal(data, &valueActress); err == nil { + c.typeName = "actress" + c.Actress = valueActress + return nil + } + valueStuntDouble := new(StuntDouble) + if err := json.Unmarshal(data, &valueStuntDouble); err == nil { + c.typeName = "stuntDouble" + c.StuntDouble = valueStuntDouble + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, c) +} + +func (c CastMember) MarshalJSON() ([]byte, error) { + switch c.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return json.Marshal(c.Actor) + case "actress": + return json.Marshal(c.Actress) + case "stuntDouble": + return json.Marshal(c.StuntDouble) + } +} + +type CastMemberVisitor interface { + VisitActor(*Actor) error + VisitActress(*Actress) error + VisitStuntDouble(*StuntDouble) error +} + +func (c *CastMember) Accept(visitor CastMemberVisitor) error { + switch c.typeName { + default: + return fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return visitor.VisitActor(c.Actor) + case "actress": + return visitor.VisitActress(c.Actress) + case "stuntDouble": + return visitor.VisitStuntDouble(c.StuntDouble) + } +} + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` +} + +func (d *Directory) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type Exception struct { + Type string + Generic *ExceptionInfo + Timeout interface{} +} + +func NewExceptionFromGeneric(value *ExceptionInfo) *Exception { + return &Exception{Type: "generic", Generic: value} +} + +func NewExceptionFromTimeout(value interface{}) *Exception { + return &Exception{Type: "timeout", Timeout: value} +} + +func (e *Exception) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "generic": + value := new(ExceptionInfo) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Generic = value + case "timeout": + value := make(map[string]interface{}) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Timeout = value + } + return nil +} + +func (e Exception) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + var marshaler = struct { + Type string `json:"type"` + *ExceptionInfo + }{ + Type: e.Type, + ExceptionInfo: e.Generic, + } + return json.Marshal(marshaler) + case "timeout": + var marshaler = struct { + Type string `json:"type"` + Timeout interface{} `json:"timeout,omitempty"` + }{ + Type: e.Type, + Timeout: e.Timeout, + } + return json.Marshal(marshaler) + } +} + +type ExceptionVisitor interface { + VisitGeneric(*ExceptionInfo) error + VisitTimeout(interface{}) error +} + +func (e *Exception) Accept(visitor ExceptionVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + return visitor.VisitGeneric(e.Generic) + case "timeout": + return visitor.VisitTimeout(e.Timeout) + } +} + +type ExceptionInfo struct { + ExceptionType string `json:"exceptionType" url:"exceptionType"` + ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` + ExceptionStacktrace string `json:"exceptionStacktrace" url:"exceptionStacktrace"` +} + +func (e *ExceptionInfo) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type ExtendedMovie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + Cast []string `json:"cast,omitempty" url:"cast,omitempty"` + type_ string +} + +func (e *ExtendedMovie) Type() string { + return e.type_ +} + +func (e *ExtendedMovie) UnmarshalJSON(data []byte) error { + type embed ExtendedMovie + var unmarshaler = struct { + embed + }{ + embed: embed(*e), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *e = ExtendedMovie(unmarshaler.embed) + e.type_ = "movie" + return nil +} + +func (e *ExtendedMovie) MarshalJSON() ([]byte, error) { + type embed ExtendedMovie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*e), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (e *ExtendedMovie) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` +} + +func (f *File) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type Metadata struct { + Type string + Extra map[string]string + Tags []string + Html string + Markdown string +} + +func NewMetadataFromHtml(value string) *Metadata { + return &Metadata{Type: "html", Html: value} +} + +func NewMetadataFromMarkdown(value string) *Metadata { + return &Metadata{Type: "markdown", Markdown: value} +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + m.Type = unmarshaler.Type + m.Extra = unmarshaler.Extra + m.Tags = unmarshaler.Tags + switch unmarshaler.Type { + case "html": + var valueUnmarshaler struct { + Html string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Html = valueUnmarshaler.Html + case "markdown": + var valueUnmarshaler struct { + Markdown string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Markdown = valueUnmarshaler.Markdown + } + return nil +} + +func (m Metadata) MarshalJSON() ([]byte, error) { + switch m.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Html string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Html: m.Html, + } + return json.Marshal(marshaler) + case "markdown": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Markdown string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Markdown: m.Markdown, + } + return json.Marshal(marshaler) + } +} + +type MetadataVisitor interface { + VisitHtml(string) error + VisitMarkdown(string) error +} + +func (m *Metadata) Accept(visitor MetadataVisitor) error { + switch m.Type { + default: + return fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + return visitor.VisitHtml(m.Html) + case "markdown": + return visitor.VisitMarkdown(m.Markdown) + } +} + +type Migration struct { + Name string `json:"name" url:"name"` + Status MigrationStatus `json:"status,omitempty" url:"status,omitempty"` +} + +func (m *Migration) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MigrationStatus string + +const ( + // The migration is running. + MigrationStatusRunning MigrationStatus = "RUNNING" + // The migration failed. + MigrationStatusFailed MigrationStatus = "FAILED" + MigrationStatusFinished MigrationStatus = "FINISHED" +) + +func NewMigrationStatusFromString(s string) (MigrationStatus, error) { + switch s { + case "RUNNING": + return MigrationStatusRunning, nil + case "FAILED": + return MigrationStatusFailed, nil + case "FINISHED": + return MigrationStatusFinished, nil + } + var t MigrationStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (m MigrationStatus) Ptr() *MigrationStatus { + return &m +} + +type Moment struct { + Id uuid.UUID `json:"id" url:"id"` + Date time.Time `json:"date" url:"date" format:"date"` + Datetime time.Time `json:"datetime" url:"datetime"` +} + +func (m *Moment) UnmarshalJSON(data []byte) error { + type embed Moment + var unmarshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Moment(unmarshaler.embed) + m.Date = unmarshaler.Date.Time() + m.Datetime = unmarshaler.Datetime.Time() + return nil +} + +func (m *Moment) MarshalJSON() ([]byte, error) { + type embed Moment + var marshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + Date: core.NewDate(m.Date), + Datetime: core.NewDateTime(m.Datetime), + } + return json.Marshal(marshaler) +} + +func (m *Moment) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + type_ string +} + +func (m *Movie) Type() string { + return m.type_ +} + +func (m *Movie) UnmarshalJSON(data []byte) error { + type embed Movie + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Movie(unmarshaler.embed) + m.type_ = "movie" + return nil +} + +func (m *Movie) MarshalJSON() ([]byte, error) { + type embed Movie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*m), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string + +type Node struct { + Name string `json:"name" url:"name"` + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + Trees []*Tree `json:"trees,omitempty" url:"trees,omitempty"` +} + +func (n *Node) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Request struct { + Request interface{} `json:"request,omitempty" url:"request,omitempty"` +} + +func (r *Request) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Response struct { + Response interface{} `json:"response,omitempty" url:"response,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type StuntDouble struct { + Name string `json:"name" url:"name"` + ActorOrActressId string `json:"actorOrActressId" url:"actorOrActressId"` +} + +func (s *StuntDouble) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type Test struct { + Type string + And bool + Or bool +} + +func NewTestFromAnd(value bool) *Test { + return &Test{Type: "and", And: value} +} + +func NewTestFromOr(value bool) *Test { + return &Test{Type: "or", Or: value} +} + +func (t *Test) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + t.Type = unmarshaler.Type + switch unmarshaler.Type { + case "and": + var valueUnmarshaler struct { + And bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.And = valueUnmarshaler.And + case "or": + var valueUnmarshaler struct { + Or bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.Or = valueUnmarshaler.Or + } + return nil +} + +func (t Test) MarshalJSON() ([]byte, error) { + switch t.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + var marshaler = struct { + Type string `json:"type"` + And bool `json:"value"` + }{ + Type: t.Type, + And: t.And, + } + return json.Marshal(marshaler) + case "or": + var marshaler = struct { + Type string `json:"type"` + Or bool `json:"value"` + }{ + Type: t.Type, + Or: t.Or, + } + return json.Marshal(marshaler) + } +} + +type TestVisitor interface { + VisitAnd(bool) error + VisitOr(bool) error +} + +func (t *Test) Accept(visitor TestVisitor) error { + switch t.Type { + default: + return fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + return visitor.VisitAnd(t.And) + case "or": + return visitor.VisitOr(t.Or) + } +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` +} + +func (t *Tree) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-model/exhaustive/.github/workflows/ci.yml b/seed/go-model/exhaustive/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/exhaustive/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/exhaustive/core/stringer.go b/seed/go-model/exhaustive/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/exhaustive/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/exhaustive/core/time.go b/seed/go-model/exhaustive/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/exhaustive/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/exhaustive/endpoints/params.go b/seed/go-model/exhaustive/endpoints/params.go new file mode 100644 index 00000000000..21100319d9e --- /dev/null +++ b/seed/go-model/exhaustive/endpoints/params.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package endpoints diff --git a/seed/go-model/exhaustive/go.mod b/seed/go-model/exhaustive/go.mod new file mode 100644 index 00000000000..664aa32c9a0 --- /dev/null +++ b/seed/go-model/exhaustive/go.mod @@ -0,0 +1,5 @@ +module github.com/exhaustive/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-model/exhaustive/go.sum b/seed/go-model/exhaustive/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-model/exhaustive/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-model/exhaustive/inlined_requests.go b/seed/go-model/exhaustive/inlined_requests.go new file mode 100644 index 00000000000..57c95ce9285 --- /dev/null +++ b/seed/go-model/exhaustive/inlined_requests.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive diff --git a/seed/go-model/exhaustive/req_with_headers.go b/seed/go-model/exhaustive/req_with_headers.go new file mode 100644 index 00000000000..57c95ce9285 --- /dev/null +++ b/seed/go-model/exhaustive/req_with_headers.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive diff --git a/seed/go-model/exhaustive/snippet.json b/seed/go-model/exhaustive/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/exhaustive/types.go b/seed/go-model/exhaustive/types.go new file mode 100644 index 00000000000..6457cdb8c9f --- /dev/null +++ b/seed/go-model/exhaustive/types.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package exhaustive + +import ( + fmt "fmt" + core "github.com/exhaustive/fern/core" +) + +type BadObjectRequestInfo struct { + Message string `json:"message" url:"message"` +} + +func (b *BadObjectRequestInfo) String() string { + if value, err := core.StringifyJSON(b); err == nil { + return value + } + return fmt.Sprintf("%#v", b) +} diff --git a/seed/go-model/exhaustive/types/types.go b/seed/go-model/exhaustive/types/types.go new file mode 100644 index 00000000000..90df02f72d9 --- /dev/null +++ b/seed/go-model/exhaustive/types/types.go @@ -0,0 +1,243 @@ +// This file was auto-generated by Fern from our API Definition. + +package types + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/exhaustive/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type WeatherReport string + +const ( + WeatherReportSunny WeatherReport = "SUNNY" + WeatherReportCloudy WeatherReport = "CLOUDY" + WeatherReportRaining WeatherReport = "RAINING" + WeatherReportSnowing WeatherReport = "SNOWING" +) + +func NewWeatherReportFromString(s string) (WeatherReport, error) { + switch s { + case "SUNNY": + return WeatherReportSunny, nil + case "CLOUDY": + return WeatherReportCloudy, nil + case "RAINING": + return WeatherReportRaining, nil + case "SNOWING": + return WeatherReportSnowing, nil + } + var t WeatherReport + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (w WeatherReport) Ptr() *WeatherReport { + return &w +} + +type NestedObjectWithOptionalField struct { + String *string `json:"string,omitempty" url:"string,omitempty"` + NestedObject *ObjectWithOptionalField `json:"NestedObject,omitempty" url:"NestedObject,omitempty"` +} + +func (n *NestedObjectWithOptionalField) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type NestedObjectWithRequiredField struct { + String string `json:"string" url:"string"` + NestedObject *ObjectWithOptionalField `json:"NestedObject,omitempty" url:"NestedObject,omitempty"` +} + +func (n *NestedObjectWithRequiredField) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type ObjectWithMapOfMap struct { + Map map[string]map[string]string `json:"map,omitempty" url:"map,omitempty"` +} + +func (o *ObjectWithMapOfMap) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type ObjectWithOptionalField struct { + String *string `json:"string,omitempty" url:"string,omitempty"` + Integer *int `json:"integer,omitempty" url:"integer,omitempty"` + Long *int64 `json:"long,omitempty" url:"long,omitempty"` + Double *float64 `json:"double,omitempty" url:"double,omitempty"` + Bool *bool `json:"bool,omitempty" url:"bool,omitempty"` + Datetime *time.Time `json:"datetime,omitempty" url:"datetime,omitempty"` + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + Uuid *uuid.UUID `json:"uuid,omitempty" url:"uuid,omitempty"` + Base64 *[]byte `json:"base64,omitempty" url:"base64,omitempty"` + List []string `json:"list,omitempty" url:"list,omitempty"` + Set []string `json:"set,omitempty" url:"set,omitempty"` + Map map[int]string `json:"map,omitempty" url:"map,omitempty"` +} + +func (o *ObjectWithOptionalField) UnmarshalJSON(data []byte) error { + type embed ObjectWithOptionalField + var unmarshaler = struct { + embed + Datetime *core.DateTime `json:"datetime,omitempty"` + Date *core.Date `json:"date,omitempty"` + }{ + embed: embed(*o), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *o = ObjectWithOptionalField(unmarshaler.embed) + o.Datetime = unmarshaler.Datetime.TimePtr() + o.Date = unmarshaler.Date.TimePtr() + return nil +} + +func (o *ObjectWithOptionalField) MarshalJSON() ([]byte, error) { + type embed ObjectWithOptionalField + var marshaler = struct { + embed + Datetime *core.DateTime `json:"datetime,omitempty"` + Date *core.Date `json:"date,omitempty"` + }{ + embed: embed(*o), + Datetime: core.NewOptionalDateTime(o.Datetime), + Date: core.NewOptionalDate(o.Date), + } + return json.Marshal(marshaler) +} + +func (o *ObjectWithOptionalField) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type ObjectWithRequiredField struct { + String string `json:"string" url:"string"` +} + +func (o *ObjectWithRequiredField) String() string { + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type Animal struct { + Animal string + Dog *Dog + Cat *Cat +} + +func NewAnimalFromDog(value *Dog) *Animal { + return &Animal{Animal: "dog", Dog: value} +} + +func NewAnimalFromCat(value *Cat) *Animal { + return &Animal{Animal: "cat", Cat: value} +} + +func (a *Animal) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Animal string `json:"animal"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + a.Animal = unmarshaler.Animal + switch unmarshaler.Animal { + case "dog": + value := new(Dog) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + a.Dog = value + case "cat": + value := new(Cat) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + a.Cat = value + } + return nil +} + +func (a Animal) MarshalJSON() ([]byte, error) { + switch a.Animal { + default: + return nil, fmt.Errorf("invalid type %s in %T", a.Animal, a) + case "dog": + var marshaler = struct { + Animal string `json:"animal"` + *Dog + }{ + Animal: a.Animal, + Dog: a.Dog, + } + return json.Marshal(marshaler) + case "cat": + var marshaler = struct { + Animal string `json:"animal"` + *Cat + }{ + Animal: a.Animal, + Cat: a.Cat, + } + return json.Marshal(marshaler) + } +} + +type AnimalVisitor interface { + VisitDog(*Dog) error + VisitCat(*Cat) error +} + +func (a *Animal) Accept(visitor AnimalVisitor) error { + switch a.Animal { + default: + return fmt.Errorf("invalid type %s in %T", a.Animal, a) + case "dog": + return visitor.VisitDog(a.Dog) + case "cat": + return visitor.VisitCat(a.Cat) + } +} + +type Cat struct { + Name string `json:"name" url:"name"` + LikesToMeow bool `json:"likesToMeow" url:"likesToMeow"` +} + +func (c *Cat) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type Dog struct { + Name string `json:"name" url:"name"` + LikesToWoof bool `json:"likesToWoof" url:"likesToWoof"` +} + +func (d *Dog) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} diff --git a/seed/go-model/extends/.github/workflows/ci.yml b/seed/go-model/extends/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/extends/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/extends/core/stringer.go b/seed/go-model/extends/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/extends/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/extends/core/time.go b/seed/go-model/extends/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/extends/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/extends/go.mod b/seed/go-model/extends/go.mod new file mode 100644 index 00000000000..11b139bc1f2 --- /dev/null +++ b/seed/go-model/extends/go.mod @@ -0,0 +1,3 @@ +module github.com/extends/fern + +go 1.13 diff --git a/seed/go-model/extends/go.sum b/seed/go-model/extends/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/extends/snippet.json b/seed/go-model/extends/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/extends/types.go b/seed/go-model/extends/types.go new file mode 100644 index 00000000000..506b19176fb --- /dev/null +++ b/seed/go-model/extends/types.go @@ -0,0 +1,56 @@ +// This file was auto-generated by Fern from our API Definition. + +package extends + +import ( + fmt "fmt" + core "github.com/extends/fern/core" +) + +type Docs struct { + Docs string `json:"docs" url:"docs"` +} + +func (d *Docs) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type ExampleType struct { + Docs string `json:"docs" url:"docs"` + Name string `json:"name" url:"name"` +} + +func (e *ExampleType) String() string { + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type Json struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` +} + +func (j *Json) String() string { + if value, err := core.StringifyJSON(j); err == nil { + return value + } + return fmt.Sprintf("%#v", j) +} + +type NestedType struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` + Name string `json:"name" url:"name"` +} + +func (n *NestedType) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} diff --git a/seed/go-model/file-download/.github/workflows/ci.yml b/seed/go-model/file-download/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/file-download/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/file-download/core/stringer.go b/seed/go-model/file-download/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/file-download/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/file-download/core/time.go b/seed/go-model/file-download/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/file-download/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/file-download/go.mod b/seed/go-model/file-download/go.mod new file mode 100644 index 00000000000..1893a4080ab --- /dev/null +++ b/seed/go-model/file-download/go.mod @@ -0,0 +1,3 @@ +module github.com/file-download/fern + +go 1.13 diff --git a/seed/go-model/file-download/go.sum b/seed/go-model/file-download/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/file-download/snippet.json b/seed/go-model/file-download/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/file-upload/.github/workflows/ci.yml b/seed/go-model/file-upload/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/file-upload/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/file-upload/core/stringer.go b/seed/go-model/file-upload/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/file-upload/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/file-upload/core/time.go b/seed/go-model/file-upload/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/file-upload/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/file-upload/go.mod b/seed/go-model/file-upload/go.mod new file mode 100644 index 00000000000..a64be340ba7 --- /dev/null +++ b/seed/go-model/file-upload/go.mod @@ -0,0 +1,3 @@ +module github.com/file-upload/fern + +go 1.13 diff --git a/seed/go-model/file-upload/go.sum b/seed/go-model/file-upload/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/file-upload/service.go b/seed/go-model/file-upload/service.go new file mode 100644 index 00000000000..b73956de120 --- /dev/null +++ b/seed/go-model/file-upload/service.go @@ -0,0 +1,19 @@ +// This file was auto-generated by Fern from our API Definition. + +package fileupload + +import ( + fmt "fmt" + core "github.com/file-upload/fern/core" +) + +type MyObject struct { + Foo string `json:"foo" url:"foo"` +} + +func (m *MyObject) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-model/file-upload/snippet.json b/seed/go-model/file-upload/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/folders/.github/workflows/ci.yml b/seed/go-model/folders/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/folders/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/folders/a/d/types.go b/seed/go-model/folders/a/d/types.go new file mode 100644 index 00000000000..7a258fa374d --- /dev/null +++ b/seed/go-model/folders/a/d/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package d + +type Foo = string diff --git a/seed/go-model/folders/core/stringer.go b/seed/go-model/folders/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/folders/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/folders/core/time.go b/seed/go-model/folders/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/folders/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/folders/go.mod b/seed/go-model/folders/go.mod new file mode 100644 index 00000000000..35536dc07bc --- /dev/null +++ b/seed/go-model/folders/go.mod @@ -0,0 +1,3 @@ +module github.com/folders/fern + +go 1.13 diff --git a/seed/go-model/folders/go.sum b/seed/go-model/folders/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/folders/snippet.json b/seed/go-model/folders/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/idempotency-headers/.github/workflows/ci.yml b/seed/go-model/idempotency-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/idempotency-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/idempotency-headers/core/stringer.go b/seed/go-model/idempotency-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/idempotency-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/idempotency-headers/core/time.go b/seed/go-model/idempotency-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/idempotency-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/idempotency-headers/go.mod b/seed/go-model/idempotency-headers/go.mod new file mode 100644 index 00000000000..928f3bed6a1 --- /dev/null +++ b/seed/go-model/idempotency-headers/go.mod @@ -0,0 +1,3 @@ +module github.com/idempotency-headers/fern + +go 1.13 diff --git a/seed/go-model/idempotency-headers/go.sum b/seed/go-model/idempotency-headers/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/idempotency-headers/payment.go b/seed/go-model/idempotency-headers/payment.go new file mode 100644 index 00000000000..5e1a0b2e1d2 --- /dev/null +++ b/seed/go-model/idempotency-headers/payment.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package fern + +import ( + fmt "fmt" +) + +type Currency string + +const ( + CurrencyUsd Currency = "USD" + CurrencyYen Currency = "YEN" +) + +func NewCurrencyFromString(s string) (Currency, error) { + switch s { + case "USD": + return CurrencyUsd, nil + case "YEN": + return CurrencyYen, nil + } + var t Currency + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c Currency) Ptr() *Currency { + return &c +} diff --git a/seed/go-model/idempotency-headers/snippet.json b/seed/go-model/idempotency-headers/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/imdb/.github/workflows/ci.yml b/seed/go-model/imdb/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/imdb/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/imdb/core/stringer.go b/seed/go-model/imdb/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/imdb/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/imdb/core/time.go b/seed/go-model/imdb/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/imdb/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/imdb/go.mod b/seed/go-model/imdb/go.mod new file mode 100644 index 00000000000..6a95cc5b0f1 --- /dev/null +++ b/seed/go-model/imdb/go.mod @@ -0,0 +1,3 @@ +module github.com/imdb/fern + +go 1.13 diff --git a/seed/go-model/imdb/go.sum b/seed/go-model/imdb/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/imdb/imdb.go b/seed/go-model/imdb/imdb.go new file mode 100644 index 00000000000..03b735fdf8b --- /dev/null +++ b/seed/go-model/imdb/imdb.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + fmt "fmt" + core "github.com/imdb/fern/core" +) + +type CreateMovieRequest struct { + Title string `json:"title" url:"title"` + Rating float64 `json:"rating" url:"rating"` +} + +func (c *CreateMovieRequest) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string diff --git a/seed/go-model/imdb/snippet.json b/seed/go-model/imdb/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/literal-headers/.github/workflows/ci.yml b/seed/go-model/literal-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/literal-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/literal-headers/core/stringer.go b/seed/go-model/literal-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/literal-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/literal-headers/core/time.go b/seed/go-model/literal-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/literal-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/literal-headers/go.mod b/seed/go-model/literal-headers/go.mod new file mode 100644 index 00000000000..2f5e49970b1 --- /dev/null +++ b/seed/go-model/literal-headers/go.mod @@ -0,0 +1,3 @@ +module github.com/literal-headers/fern + +go 1.13 diff --git a/seed/go-model/literal-headers/go.sum b/seed/go-model/literal-headers/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/literal-headers/snippet.json b/seed/go-model/literal-headers/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/literal-headers/with_non_literal_headers.go b/seed/go-model/literal-headers/with_non_literal_headers.go new file mode 100644 index 00000000000..f89ece46396 --- /dev/null +++ b/seed/go-model/literal-headers/with_non_literal_headers.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package literalheaders diff --git a/seed/go-model/literal/.github/workflows/ci.yml b/seed/go-model/literal/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/literal/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/literal/core/stringer.go b/seed/go-model/literal/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/literal/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/literal/core/time.go b/seed/go-model/literal/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/literal/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/literal/go.mod b/seed/go-model/literal/go.mod new file mode 100644 index 00000000000..3f1d855d469 --- /dev/null +++ b/seed/go-model/literal/go.mod @@ -0,0 +1,3 @@ +module github.com/literal/fern + +go 1.13 diff --git a/seed/go-model/literal/go.sum b/seed/go-model/literal/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/literal/headers.go b/seed/go-model/literal/headers.go new file mode 100644 index 00000000000..d14a0e8da9a --- /dev/null +++ b/seed/go-model/literal/headers.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal diff --git a/seed/go-model/literal/inlined.go b/seed/go-model/literal/inlined.go new file mode 100644 index 00000000000..d14a0e8da9a --- /dev/null +++ b/seed/go-model/literal/inlined.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal diff --git a/seed/go-model/literal/query.go b/seed/go-model/literal/query.go new file mode 100644 index 00000000000..d14a0e8da9a --- /dev/null +++ b/seed/go-model/literal/query.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal diff --git a/seed/go-model/literal/reference.go b/seed/go-model/literal/reference.go new file mode 100644 index 00000000000..d3264eb0370 --- /dev/null +++ b/seed/go-model/literal/reference.go @@ -0,0 +1,60 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendRequest struct { + Query string `json:"query" url:"query"` + prompt string + stream bool +} + +func (s *SendRequest) Prompt() string { + return s.prompt +} + +func (s *SendRequest) Stream() bool { + return s.stream +} + +func (s *SendRequest) UnmarshalJSON(data []byte) error { + type embed SendRequest + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendRequest(unmarshaler.embed) + s.prompt = "You are a helpful assistant" + s.stream = false + return nil +} + +func (s *SendRequest) MarshalJSON() ([]byte, error) { + type embed SendRequest + var marshaler = struct { + embed + Prompt string `json:"prompt"` + Stream bool `json:"stream"` + }{ + embed: embed(*s), + Prompt: "You are a helpful assistant", + Stream: false, + } + return json.Marshal(marshaler) +} + +func (s *SendRequest) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-model/literal/snippet.json b/seed/go-model/literal/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/literal/types.go b/seed/go-model/literal/types.go new file mode 100644 index 00000000000..60870e635bd --- /dev/null +++ b/seed/go-model/literal/types.go @@ -0,0 +1,53 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendResponse struct { + Message string `json:"message" url:"message"` + Status int `json:"status" url:"status"` + success bool +} + +func (s *SendResponse) Success() bool { + return s.success +} + +func (s *SendResponse) UnmarshalJSON(data []byte) error { + type embed SendResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendResponse(unmarshaler.embed) + s.success = true + return nil +} + +func (s *SendResponse) MarshalJSON() ([]byte, error) { + type embed SendResponse + var marshaler = struct { + embed + Success bool `json:"success"` + }{ + embed: embed(*s), + Success: true, + } + return json.Marshal(marshaler) +} + +func (s *SendResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-model/multi-url-environment/.github/workflows/ci.yml b/seed/go-model/multi-url-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/multi-url-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/multi-url-environment/core/stringer.go b/seed/go-model/multi-url-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/multi-url-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/multi-url-environment/core/time.go b/seed/go-model/multi-url-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/multi-url-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/multi-url-environment/ec_2.go b/seed/go-model/multi-url-environment/ec_2.go new file mode 100644 index 00000000000..180c4f16c1a --- /dev/null +++ b/seed/go-model/multi-url-environment/ec_2.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment diff --git a/seed/go-model/multi-url-environment/go.mod b/seed/go-model/multi-url-environment/go.mod new file mode 100644 index 00000000000..3959168c8db --- /dev/null +++ b/seed/go-model/multi-url-environment/go.mod @@ -0,0 +1,3 @@ +module github.com/multi-url-environment/fern + +go 1.13 diff --git a/seed/go-model/multi-url-environment/go.sum b/seed/go-model/multi-url-environment/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/multi-url-environment/s_3.go b/seed/go-model/multi-url-environment/s_3.go new file mode 100644 index 00000000000..180c4f16c1a --- /dev/null +++ b/seed/go-model/multi-url-environment/s_3.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment diff --git a/seed/go-model/multi-url-environment/snippet.json b/seed/go-model/multi-url-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/no-environment/.github/workflows/ci.yml b/seed/go-model/no-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/no-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/no-environment/core/stringer.go b/seed/go-model/no-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/no-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/no-environment/core/time.go b/seed/go-model/no-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/no-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/no-environment/go.mod b/seed/go-model/no-environment/go.mod new file mode 100644 index 00000000000..f98982ca0c7 --- /dev/null +++ b/seed/go-model/no-environment/go.mod @@ -0,0 +1,3 @@ +module github.com/no-environment/fern + +go 1.13 diff --git a/seed/go-model/no-environment/go.sum b/seed/go-model/no-environment/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/no-environment/snippet.json b/seed/go-model/no-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/object/.github/workflows/ci.yml b/seed/go-model/object/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/object/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/object/core/stringer.go b/seed/go-model/object/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/object/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/object/core/time.go b/seed/go-model/object/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/object/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/object/go.mod b/seed/go-model/object/go.mod new file mode 100644 index 00000000000..af8c8515ad7 --- /dev/null +++ b/seed/go-model/object/go.mod @@ -0,0 +1,5 @@ +module github.com/object/fern + +go 1.13 + +require github.com/google/uuid v1.4.0 diff --git a/seed/go-model/object/go.sum b/seed/go-model/object/go.sum new file mode 100644 index 00000000000..fef9ecd2323 --- /dev/null +++ b/seed/go-model/object/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/seed/go-model/object/snippet.json b/seed/go-model/object/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/object/types.go b/seed/go-model/object/types.go new file mode 100644 index 00000000000..18d4498179b --- /dev/null +++ b/seed/go-model/object/types.go @@ -0,0 +1,92 @@ +// This file was auto-generated by Fern from our API Definition. + +package object + +import ( + json "encoding/json" + fmt "fmt" + uuid "github.com/google/uuid" + core "github.com/object/fern/core" + time "time" +) + +type Name struct { + Id string `json:"id" url:"id"` + Value string `json:"value" url:"value"` +} + +func (n *Name) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +// Exercises all of the built-in types. +type Type struct { + One int `json:"one" url:"one"` + Two float64 `json:"two" url:"two"` + Three string `json:"three" url:"three"` + Four bool `json:"four" url:"four"` + Five int64 `json:"five" url:"five"` + Six time.Time `json:"six" url:"six"` + Seven time.Time `json:"seven" url:"seven" format:"date"` + Eight uuid.UUID `json:"eight" url:"eight"` + Nine []byte `json:"nine" url:"nine"` + Ten []int `json:"ten,omitempty" url:"ten,omitempty"` + Eleven []float64 `json:"eleven,omitempty" url:"eleven,omitempty"` + Twelve map[string]bool `json:"twelve,omitempty" url:"twelve,omitempty"` + Thirteen *int64 `json:"thirteen,omitempty" url:"thirteen,omitempty"` + Fourteen interface{} `json:"fourteen,omitempty" url:"fourteen,omitempty"` + Fifteen [][]int `json:"fifteen,omitempty" url:"fifteen,omitempty"` + Sixteen []map[string]int `json:"sixteen,omitempty" url:"sixteen,omitempty"` + Seventeen []*uuid.UUID `json:"seventeen,omitempty" url:"seventeen,omitempty"` + Nineteen *Name `json:"nineteen,omitempty" url:"nineteen,omitempty"` + eighteen string +} + +func (t *Type) Eighteen() string { + return t.eighteen +} + +func (t *Type) UnmarshalJSON(data []byte) error { + type embed Type + var unmarshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + }{ + embed: embed(*t), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *t = Type(unmarshaler.embed) + t.Six = unmarshaler.Six.Time() + t.Seven = unmarshaler.Seven.Time() + t.eighteen = "eighteen" + return nil +} + +func (t *Type) MarshalJSON() ([]byte, error) { + type embed Type + var marshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + Eighteen string `json:"eighteen"` + }{ + embed: embed(*t), + Six: core.NewDateTime(t.Six), + Seven: core.NewDate(t.Seven), + Eighteen: "eighteen", + } + return json.Marshal(marshaler) +} + +func (t *Type) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-model/objects-with-imports/.github/workflows/ci.yml b/seed/go-model/objects-with-imports/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/objects-with-imports/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/objects-with-imports/commons/types.go b/seed/go-model/objects-with-imports/commons/types.go new file mode 100644 index 00000000000..90bf2d4e873 --- /dev/null +++ b/seed/go-model/objects-with-imports/commons/types.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + fmt "fmt" + core "github.com/objects-with-imports/fern/core" +) + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` +} + +func (m *Metadata) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-model/objects-with-imports/core/stringer.go b/seed/go-model/objects-with-imports/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/objects-with-imports/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/objects-with-imports/core/time.go b/seed/go-model/objects-with-imports/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/objects-with-imports/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/objects-with-imports/file/types.go b/seed/go-model/objects-with-imports/file/types.go new file mode 100644 index 00000000000..dd9224edba5 --- /dev/null +++ b/seed/go-model/objects-with-imports/file/types.go @@ -0,0 +1,22 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +import ( + fmt "fmt" + fern "github.com/objects-with-imports/fern" + core "github.com/objects-with-imports/fern/core" +) + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*fern.File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` +} + +func (d *Directory) String() string { + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} diff --git a/seed/go-model/objects-with-imports/go.mod b/seed/go-model/objects-with-imports/go.mod new file mode 100644 index 00000000000..bc1c954560c --- /dev/null +++ b/seed/go-model/objects-with-imports/go.mod @@ -0,0 +1,3 @@ +module github.com/objects-with-imports/fern + +go 1.13 diff --git a/seed/go-model/objects-with-imports/go.sum b/seed/go-model/objects-with-imports/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/objects-with-imports/snippet.json b/seed/go-model/objects-with-imports/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/objects-with-imports/types.go b/seed/go-model/objects-with-imports/types.go new file mode 100644 index 00000000000..971421132b2 --- /dev/null +++ b/seed/go-model/objects-with-imports/types.go @@ -0,0 +1,70 @@ +// This file was auto-generated by Fern from our API Definition. + +package objectswithimports + +import ( + fmt "fmt" + commons "github.com/objects-with-imports/fern/commons" + core "github.com/objects-with-imports/fern/core" +) + +type Node struct { + Id string `json:"id" url:"id"` + Label *string `json:"label,omitempty" url:"label,omitempty"` + Metadata *commons.Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` +} + +func (n *Node) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` +} + +func (t *Tree) String() string { + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` + Info FileInfo `json:"info,omitempty" url:"info,omitempty"` +} + +func (f *File) String() string { + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type FileInfo string + +const ( + // A regular file (e.g. foo.txt). + FileInfoRegular FileInfo = "REGULAR" + // A directory (e.g. foo/). + FileInfoDirectory FileInfo = "DIRECTORY" +) + +func NewFileInfoFromString(s string) (FileInfo, error) { + switch s { + case "REGULAR": + return FileInfoRegular, nil + case "DIRECTORY": + return FileInfoDirectory, nil + } + var t FileInfo + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (f FileInfo) Ptr() *FileInfo { + return &f +} diff --git a/seed/go-model/optional/.github/workflows/ci.yml b/seed/go-model/optional/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/optional/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/optional/core/stringer.go b/seed/go-model/optional/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/optional/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/optional/core/time.go b/seed/go-model/optional/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/optional/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/optional/go.mod b/seed/go-model/optional/go.mod new file mode 100644 index 00000000000..6e2c82f2fe2 --- /dev/null +++ b/seed/go-model/optional/go.mod @@ -0,0 +1,3 @@ +module github.com/optional/fern + +go 1.13 diff --git a/seed/go-model/optional/go.sum b/seed/go-model/optional/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/optional/snippet.json b/seed/go-model/optional/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/package-yml/.github/workflows/ci.yml b/seed/go-model/package-yml/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/package-yml/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/package-yml/core/stringer.go b/seed/go-model/package-yml/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/package-yml/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/package-yml/core/time.go b/seed/go-model/package-yml/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/package-yml/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/package-yml/go.mod b/seed/go-model/package-yml/go.mod new file mode 100644 index 00000000000..a48f613f354 --- /dev/null +++ b/seed/go-model/package-yml/go.mod @@ -0,0 +1,3 @@ +module github.com/package-yml/fern + +go 1.13 diff --git a/seed/go-model/package-yml/go.sum b/seed/go-model/package-yml/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/package-yml/snippet.json b/seed/go-model/package-yml/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/plain-text/.github/workflows/ci.yml b/seed/go-model/plain-text/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/plain-text/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/plain-text/core/stringer.go b/seed/go-model/plain-text/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/plain-text/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/plain-text/core/time.go b/seed/go-model/plain-text/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/plain-text/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/plain-text/go.mod b/seed/go-model/plain-text/go.mod new file mode 100644 index 00000000000..47b16a3473c --- /dev/null +++ b/seed/go-model/plain-text/go.mod @@ -0,0 +1,3 @@ +module github.com/plain-text/fern + +go 1.13 diff --git a/seed/go-model/plain-text/go.sum b/seed/go-model/plain-text/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/plain-text/snippet.json b/seed/go-model/plain-text/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/query-parameters/.github/workflows/ci.yml b/seed/go-model/query-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/query-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/query-parameters/core/stringer.go b/seed/go-model/query-parameters/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/query-parameters/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/query-parameters/core/time.go b/seed/go-model/query-parameters/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/query-parameters/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/query-parameters/go.mod b/seed/go-model/query-parameters/go.mod new file mode 100644 index 00000000000..f91e48c9c2b --- /dev/null +++ b/seed/go-model/query-parameters/go.mod @@ -0,0 +1,3 @@ +module github.com/query-parameters/fern + +go 1.13 diff --git a/seed/go-model/query-parameters/go.sum b/seed/go-model/query-parameters/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/query-parameters/snippet.json b/seed/go-model/query-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/query-parameters/user.go b/seed/go-model/query-parameters/user.go new file mode 100644 index 00000000000..087891dc8c2 --- /dev/null +++ b/seed/go-model/query-parameters/user.go @@ -0,0 +1,32 @@ +// This file was auto-generated by Fern from our API Definition. + +package queryparameters + +import ( + fmt "fmt" + core "github.com/query-parameters/fern/core" +) + +type NestedUser struct { + Name string `json:"name" url:"name"` + User *User `json:"user,omitempty" url:"user,omitempty"` +} + +func (n *NestedUser) String() string { + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type User struct { + Name string `json:"name" url:"name"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` +} + +func (u *User) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/response-property/.github/workflows/ci.yml b/seed/go-model/response-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/response-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/response-property/core/stringer.go b/seed/go-model/response-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/response-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/response-property/core/time.go b/seed/go-model/response-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/response-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/response-property/go.mod b/seed/go-model/response-property/go.mod new file mode 100644 index 00000000000..a53aca15467 --- /dev/null +++ b/seed/go-model/response-property/go.mod @@ -0,0 +1,3 @@ +module github.com/response-property/fern + +go 1.13 diff --git a/seed/go-model/response-property/go.sum b/seed/go-model/response-property/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/response-property/service.go b/seed/go-model/response-property/service.go new file mode 100644 index 00000000000..e03f806dd21 --- /dev/null +++ b/seed/go-model/response-property/service.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package responseproperty + +import ( + fmt "fmt" + core "github.com/response-property/fern/core" +) + +type OptionalStringResponse = *StringResponse + +type StringResponse struct { + Data string `json:"data" url:"data"` +} + +func (s *StringResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type OptionalWithDocs = *WithDocs + +type Response struct { + Metadata map[string]string `json:"metadata,omitempty" url:"metadata,omitempty"` + Docs string `json:"docs" url:"docs"` + Data *Movie `json:"data,omitempty" url:"data,omitempty"` +} + +func (r *Response) String() string { + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} diff --git a/seed/go-model/response-property/snippet.json b/seed/go-model/response-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/response-property/types.go b/seed/go-model/response-property/types.go new file mode 100644 index 00000000000..4e75908efda --- /dev/null +++ b/seed/go-model/response-property/types.go @@ -0,0 +1,42 @@ +// This file was auto-generated by Fern from our API Definition. + +package responseproperty + +import ( + fmt "fmt" + core "github.com/response-property/fern/core" +) + +type WithMetadata struct { + Metadata map[string]string `json:"metadata,omitempty" url:"metadata,omitempty"` +} + +func (w *WithMetadata) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} + +type Movie struct { + Id string `json:"id" url:"id"` + Name string `json:"name" url:"name"` +} + +func (m *Movie) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type WithDocs struct { + Docs string `json:"docs" url:"docs"` +} + +func (w *WithDocs) String() string { + if value, err := core.StringifyJSON(w); err == nil { + return value + } + return fmt.Sprintf("%#v", w) +} diff --git a/seed/go-model/seed.yml b/seed/go-model/seed.yml new file mode 100644 index 00000000000..efadc0f1364 --- /dev/null +++ b/seed/go-model/seed.yml @@ -0,0 +1,31 @@ +irVersion: v33 +docker: fernapi/fern-go-model:latest +dockerCommand: docker build -f ./generators/go/docker/Dockerfile.model -t fernapi/fern-go-model:latest ./generators/go +language: go +generatorType: sdk +defaultOutputMode: github +fixtures: + streaming: + - outputFolder: . + outputVersion: v2.0.0 + customConfig: + packageName: stream + module: + path: github.com/fern-api/stream-go + idempotency-headers: + - outputFolder: . + outputVersion: 0.0.1 + customConfig: + packageName: fern + module: + path: github.com/idempotency-headers/fern + includeLegacyClientOptions: true +scripts: + - docker: golang:1.18-alpine + commands: + - CGO_ENABLED=0 go test ./... +allowedFailures: + - exhaustive + - reserved-keywords + - trace + - websocket diff --git a/seed/go-model/single-url-environment-default/.github/workflows/ci.yml b/seed/go-model/single-url-environment-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/single-url-environment-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/single-url-environment-default/core/stringer.go b/seed/go-model/single-url-environment-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/single-url-environment-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/single-url-environment-default/core/time.go b/seed/go-model/single-url-environment-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/single-url-environment-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/single-url-environment-default/go.mod b/seed/go-model/single-url-environment-default/go.mod new file mode 100644 index 00000000000..b8e5300ef77 --- /dev/null +++ b/seed/go-model/single-url-environment-default/go.mod @@ -0,0 +1,3 @@ +module github.com/single-url-environment-default/fern + +go 1.13 diff --git a/seed/go-model/single-url-environment-default/go.sum b/seed/go-model/single-url-environment-default/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/single-url-environment-default/snippet.json b/seed/go-model/single-url-environment-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/single-url-environment-no-default/.github/workflows/ci.yml b/seed/go-model/single-url-environment-no-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/single-url-environment-no-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/single-url-environment-no-default/core/stringer.go b/seed/go-model/single-url-environment-no-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/single-url-environment-no-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/single-url-environment-no-default/core/time.go b/seed/go-model/single-url-environment-no-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/single-url-environment-no-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/single-url-environment-no-default/go.mod b/seed/go-model/single-url-environment-no-default/go.mod new file mode 100644 index 00000000000..35b4fb9c8f8 --- /dev/null +++ b/seed/go-model/single-url-environment-no-default/go.mod @@ -0,0 +1,3 @@ +module github.com/single-url-environment-no-default/fern + +go 1.13 diff --git a/seed/go-model/single-url-environment-no-default/go.sum b/seed/go-model/single-url-environment-no-default/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/single-url-environment-no-default/snippet.json b/seed/go-model/single-url-environment-no-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/streaming/.github/workflows/ci.yml b/seed/go-model/streaming/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/streaming/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/streaming/core/stringer.go b/seed/go-model/streaming/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/streaming/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/streaming/core/time.go b/seed/go-model/streaming/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/streaming/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/streaming/dummy.go b/seed/go-model/streaming/dummy.go new file mode 100644 index 00000000000..6f73705d82c --- /dev/null +++ b/seed/go-model/streaming/dummy.go @@ -0,0 +1,20 @@ +// This file was auto-generated by Fern from our API Definition. + +package stream + +import ( + fmt "fmt" + core "github.com/fern-api/stream-go/v2/core" +) + +type StreamResponse struct { + Id string `json:"id" url:"id"` + Name *string `json:"name,omitempty" url:"name,omitempty"` +} + +func (s *StreamResponse) String() string { + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-model/streaming/go.mod b/seed/go-model/streaming/go.mod new file mode 100644 index 00000000000..473c2cf54a2 --- /dev/null +++ b/seed/go-model/streaming/go.mod @@ -0,0 +1,3 @@ +module github.com/fern-api/stream-go/v2 + +go 1.18 diff --git a/seed/go-model/streaming/snippet.json b/seed/go-model/streaming/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/undiscriminated-unions/.github/workflows/ci.yml b/seed/go-model/undiscriminated-unions/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/undiscriminated-unions/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/undiscriminated-unions/core/stringer.go b/seed/go-model/undiscriminated-unions/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/undiscriminated-unions/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/undiscriminated-unions/core/time.go b/seed/go-model/undiscriminated-unions/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/undiscriminated-unions/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/undiscriminated-unions/go.mod b/seed/go-model/undiscriminated-unions/go.mod new file mode 100644 index 00000000000..a45336a7da2 --- /dev/null +++ b/seed/go-model/undiscriminated-unions/go.mod @@ -0,0 +1,3 @@ +module github.com/undiscriminated-unions/fern + +go 1.13 diff --git a/seed/go-model/undiscriminated-unions/go.sum b/seed/go-model/undiscriminated-unions/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/undiscriminated-unions/snippet.json b/seed/go-model/undiscriminated-unions/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/undiscriminated-unions/union.go b/seed/go-model/undiscriminated-unions/union.go new file mode 100644 index 00000000000..126f606cab3 --- /dev/null +++ b/seed/go-model/undiscriminated-unions/union.go @@ -0,0 +1,114 @@ +// This file was auto-generated by Fern from our API Definition. + +package undiscriminatedunions + +import ( + json "encoding/json" + fmt "fmt" +) + +// Several different types are accepted. +type MyUnion struct { + typeName string + String string + StringList []string + Integer int + IntegerList []int + IntegerListList [][]int +} + +func NewMyUnionFromString(value string) *MyUnion { + return &MyUnion{typeName: "string", String: value} +} + +func NewMyUnionFromStringList(value []string) *MyUnion { + return &MyUnion{typeName: "stringList", StringList: value} +} + +func NewMyUnionFromInteger(value int) *MyUnion { + return &MyUnion{typeName: "integer", Integer: value} +} + +func NewMyUnionFromIntegerList(value []int) *MyUnion { + return &MyUnion{typeName: "integerList", IntegerList: value} +} + +func NewMyUnionFromIntegerListList(value [][]int) *MyUnion { + return &MyUnion{typeName: "integerListList", IntegerListList: value} +} + +func (m *MyUnion) UnmarshalJSON(data []byte) error { + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.typeName = "string" + m.String = valueString + return nil + } + var valueStringList []string + if err := json.Unmarshal(data, &valueStringList); err == nil { + m.typeName = "stringList" + m.StringList = valueStringList + return nil + } + var valueInteger int + if err := json.Unmarshal(data, &valueInteger); err == nil { + m.typeName = "integer" + m.Integer = valueInteger + return nil + } + var valueIntegerList []int + if err := json.Unmarshal(data, &valueIntegerList); err == nil { + m.typeName = "integerList" + m.IntegerList = valueIntegerList + return nil + } + var valueIntegerListList [][]int + if err := json.Unmarshal(data, &valueIntegerListList); err == nil { + m.typeName = "integerListList" + m.IntegerListList = valueIntegerListList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MyUnion) MarshalJSON() ([]byte, error) { + switch m.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return json.Marshal(m.String) + case "stringList": + return json.Marshal(m.StringList) + case "integer": + return json.Marshal(m.Integer) + case "integerList": + return json.Marshal(m.IntegerList) + case "integerListList": + return json.Marshal(m.IntegerListList) + } +} + +type MyUnionVisitor interface { + VisitString(string) error + VisitStringList([]string) error + VisitInteger(int) error + VisitIntegerList([]int) error + VisitIntegerListList([][]int) error +} + +func (m *MyUnion) Accept(visitor MyUnionVisitor) error { + switch m.typeName { + default: + return fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return visitor.VisitString(m.String) + case "stringList": + return visitor.VisitStringList(m.StringList) + case "integer": + return visitor.VisitInteger(m.Integer) + case "integerList": + return visitor.VisitIntegerList(m.IntegerList) + case "integerListList": + return visitor.VisitIntegerListList(m.IntegerListList) + } +} diff --git a/seed/go-model/unknown/.github/workflows/ci.yml b/seed/go-model/unknown/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/unknown/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/unknown/core/stringer.go b/seed/go-model/unknown/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/unknown/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/unknown/core/time.go b/seed/go-model/unknown/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/unknown/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/unknown/go.mod b/seed/go-model/unknown/go.mod new file mode 100644 index 00000000000..c16b006703e --- /dev/null +++ b/seed/go-model/unknown/go.mod @@ -0,0 +1,3 @@ +module github.com/unknown/fern + +go 1.13 diff --git a/seed/go-model/unknown/go.sum b/seed/go-model/unknown/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/unknown/snippet.json b/seed/go-model/unknown/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/unknown/types.go b/seed/go-model/unknown/types.go new file mode 100644 index 00000000000..0600ff3a611 --- /dev/null +++ b/seed/go-model/unknown/types.go @@ -0,0 +1,21 @@ +// This file was auto-generated by Fern from our API Definition. + +package unknownasany + +import ( + fmt "fmt" + core "github.com/unknown/fern/core" +) + +type MyAlias = interface{} + +type MyObject struct { + Unknown interface{} `json:"unknown,omitempty" url:"unknown,omitempty"` +} + +func (m *MyObject) String() string { + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-model/variables/.github/workflows/ci.yml b/seed/go-model/variables/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/variables/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/variables/core/stringer.go b/seed/go-model/variables/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/variables/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/variables/core/time.go b/seed/go-model/variables/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/variables/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/variables/go.mod b/seed/go-model/variables/go.mod new file mode 100644 index 00000000000..46aea28b366 --- /dev/null +++ b/seed/go-model/variables/go.mod @@ -0,0 +1,3 @@ +module github.com/variables/fern + +go 1.13 diff --git a/seed/go-model/variables/go.sum b/seed/go-model/variables/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/variables/snippet.json b/seed/go-model/variables/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/alias/.github/workflows/ci.yml b/seed/go-sdk/alias/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/alias/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/alias/client/client.go b/seed/go-sdk/alias/client/client.go new file mode 100644 index 00000000000..7e92778d548 --- /dev/null +++ b/seed/go-sdk/alias/client/client.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/alias/fern/core" + option "github.com/alias/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} diff --git a/seed/go-sdk/alias/client/client_test.go b/seed/go-sdk/alias/client/client_test.go new file mode 100644 index 00000000000..9e0a12d5c15 --- /dev/null +++ b/seed/go-sdk/alias/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/alias/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/bytes/core/core.go b/seed/go-sdk/alias/core/core.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/core.go rename to seed/go-sdk/alias/core/core.go diff --git a/generators/go/seed/sdk/bytes/core/core_test.go b/seed/go-sdk/alias/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/core_test.go rename to seed/go-sdk/alias/core/core_test.go diff --git a/generators/go/seed/sdk/bytes/core/query.go b/seed/go-sdk/alias/core/query.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/query.go rename to seed/go-sdk/alias/core/query.go diff --git a/generators/go/seed/sdk/bytes/core/query_test.go b/seed/go-sdk/alias/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/query_test.go rename to seed/go-sdk/alias/core/query_test.go diff --git a/generators/go/seed/sdk/literal/core/request_option.go b/seed/go-sdk/alias/core/request_option.go similarity index 97% rename from generators/go/seed/sdk/literal/core/request_option.go rename to seed/go-sdk/alias/core/request_option.go index 115332d66e1..8b460cfdd2a 100644 --- a/generators/go/seed/sdk/literal/core/request_option.go +++ b/seed/go-sdk/alias/core/request_option.go @@ -43,7 +43,7 @@ func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } func (r *RequestOptions) cloneHeader() http.Header { headers := r.HTTPHeader.Clone() headers.Set("X-Fern-Language", "Go") - headers.Set("X-Fern-SDK-Name", "github.com/literal/fern") + headers.Set("X-Fern-SDK-Name", "github.com/alias/fern") headers.Set("X-Fern-SDK-Version", "0.0.1") return headers } diff --git a/generators/go/seed/sdk/bytes/core/retrier.go b/seed/go-sdk/alias/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/retrier.go rename to seed/go-sdk/alias/core/retrier.go diff --git a/seed/go-sdk/alias/core/stringer.go b/seed/go-sdk/alias/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/alias/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/alias/core/time.go b/seed/go-sdk/alias/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/alias/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/alias/go.mod b/seed/go-sdk/alias/go.mod new file mode 100644 index 00000000000..9f45bb0bd32 --- /dev/null +++ b/seed/go-sdk/alias/go.mod @@ -0,0 +1,9 @@ +module github.com/alias/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/bytes/go.sum b/seed/go-sdk/alias/go.sum similarity index 100% rename from generators/go/seed/sdk/bytes/go.sum rename to seed/go-sdk/alias/go.sum diff --git a/seed/go-sdk/alias/option/request_option.go b/seed/go-sdk/alias/option/request_option.go new file mode 100644 index 00000000000..8a02bddb46b --- /dev/null +++ b/seed/go-sdk/alias/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/alias/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/alias/pointer.go b/seed/go-sdk/alias/pointer.go new file mode 100644 index 00000000000..f1987a4f37e --- /dev/null +++ b/seed/go-sdk/alias/pointer.go @@ -0,0 +1,132 @@ +package alias + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/alias/snippet.json b/seed/go-sdk/alias/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/alias/types.go b/seed/go-sdk/alias/types.go new file mode 100644 index 00000000000..8159d797ed8 --- /dev/null +++ b/seed/go-sdk/alias/types.go @@ -0,0 +1,46 @@ +// This file was auto-generated by Fern from our API Definition. + +package alias + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/alias/fern/core" +) + +// Object is an alias for a type. +type Object = *Type + +// A simple type with just a name. +type Type struct { + Id TypeId `json:"id" url:"id"` + Name string `json:"name" url:"name"` + + _rawJSON json.RawMessage +} + +func (t *Type) UnmarshalJSON(data []byte) error { + type unmarshaler Type + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *t = Type(value) + t._rawJSON = json.RawMessage(data) + return nil +} + +func (t *Type) String() string { + if len(t._rawJSON) > 0 { + if value, err := core.StringifyJSON(t._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +// An alias for type IDs. +type TypeId = string diff --git a/seed/go-sdk/api-wide-base-path/.github/workflows/ci.yml b/seed/go-sdk/api-wide-base-path/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/api-wide-base-path/client/client.go b/seed/go-sdk/api-wide-base-path/client/client.go new file mode 100644 index 00000000000..56d714f0e86 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/api-wide-base-path/fern/core" + option "github.com/api-wide-base-path/fern/option" + service "github.com/api-wide-base-path/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/api-wide-base-path/client/client_test.go b/seed/go-sdk/api-wide-base-path/client/client_test.go new file mode 100644 index 00000000000..75a6e6caaa1 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/api-wide-base-path/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/enum/core/core.go b/seed/go-sdk/api-wide-base-path/core/core.go similarity index 100% rename from generators/go/seed/sdk/enum/core/core.go rename to seed/go-sdk/api-wide-base-path/core/core.go diff --git a/generators/go/seed/sdk/enum/core/core_test.go b/seed/go-sdk/api-wide-base-path/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/enum/core/core_test.go rename to seed/go-sdk/api-wide-base-path/core/core_test.go diff --git a/generators/go/seed/sdk/enum/core/query.go b/seed/go-sdk/api-wide-base-path/core/query.go similarity index 100% rename from generators/go/seed/sdk/enum/core/query.go rename to seed/go-sdk/api-wide-base-path/core/query.go diff --git a/generators/go/seed/sdk/enum/core/query_test.go b/seed/go-sdk/api-wide-base-path/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/enum/core/query_test.go rename to seed/go-sdk/api-wide-base-path/core/query_test.go diff --git a/seed/go-sdk/api-wide-base-path/core/request_option.go b/seed/go-sdk/api-wide-base-path/core/request_option.go new file mode 100644 index 00000000000..6eea8a7737d --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/api-wide-base-path/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/generators/go/seed/sdk/enum/core/retrier.go b/seed/go-sdk/api-wide-base-path/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/enum/core/retrier.go rename to seed/go-sdk/api-wide-base-path/core/retrier.go diff --git a/seed/go-sdk/api-wide-base-path/core/stringer.go b/seed/go-sdk/api-wide-base-path/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/api-wide-base-path/core/time.go b/seed/go-sdk/api-wide-base-path/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/api-wide-base-path/go.mod b/seed/go-sdk/api-wide-base-path/go.mod new file mode 100644 index 00000000000..9a7011b7515 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/go.mod @@ -0,0 +1,9 @@ +module github.com/api-wide-base-path/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/enum/go.sum b/seed/go-sdk/api-wide-base-path/go.sum similarity index 100% rename from generators/go/seed/sdk/enum/go.sum rename to seed/go-sdk/api-wide-base-path/go.sum diff --git a/seed/go-sdk/api-wide-base-path/option/request_option.go b/seed/go-sdk/api-wide-base-path/option/request_option.go new file mode 100644 index 00000000000..5ab090482d4 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/api-wide-base-path/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/api-wide-base-path/pointer.go b/seed/go-sdk/api-wide-base-path/pointer.go new file mode 100644 index 00000000000..931246d4f89 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/pointer.go @@ -0,0 +1,132 @@ +package apiwidebasepath + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/api-wide-base-path/service/client.go b/seed/go-sdk/api-wide-base-path/service/client.go new file mode 100644 index 00000000000..70815d1d5a5 --- /dev/null +++ b/seed/go-sdk/api-wide-base-path/service/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + core "github.com/api-wide-base-path/fern/core" + option "github.com/api-wide-base-path/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Post( + ctx context.Context, + pathParam string, + serviceParam string, + endpointParam int, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"test/%v/%v/%v", pathParam, serviceParam, endpointParam) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/api-wide-base-path/snippet.json b/seed/go-sdk/api-wide-base-path/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/audiences/.github/workflows/ci.yml b/seed/go-sdk/audiences/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/audiences/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/audiences/client/client.go b/seed/go-sdk/audiences/client/client.go new file mode 100644 index 00000000000..ed9f360b508 --- /dev/null +++ b/seed/go-sdk/audiences/client/client.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/audiences/fern/core" + folderaclient "github.com/audiences/fern/foldera/client" + foo "github.com/audiences/fern/foo" + option "github.com/audiences/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + FolderA *folderaclient.Client + Foo *foo.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + FolderA: folderaclient.NewClient(opts...), + Foo: foo.NewClient(opts...), + } +} diff --git a/seed/go-sdk/audiences/client/client_test.go b/seed/go-sdk/audiences/client/client_test.go new file mode 100644 index 00000000000..fd14ff556c6 --- /dev/null +++ b/seed/go-sdk/audiences/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/audiences/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/file-upload/core/core.go b/seed/go-sdk/audiences/core/core.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/core.go rename to seed/go-sdk/audiences/core/core.go diff --git a/generators/go/seed/sdk/file-upload/core/core_test.go b/seed/go-sdk/audiences/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/core_test.go rename to seed/go-sdk/audiences/core/core_test.go diff --git a/generators/go/seed/sdk/file-upload/core/query.go b/seed/go-sdk/audiences/core/query.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/query.go rename to seed/go-sdk/audiences/core/query.go diff --git a/generators/go/seed/sdk/file-upload/core/query_test.go b/seed/go-sdk/audiences/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/query_test.go rename to seed/go-sdk/audiences/core/query_test.go diff --git a/seed/go-sdk/audiences/core/request_option.go b/seed/go-sdk/audiences/core/request_option.go new file mode 100644 index 00000000000..c6dc22f007c --- /dev/null +++ b/seed/go-sdk/audiences/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/audiences/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/generators/go/seed/sdk/file-upload/core/retrier.go b/seed/go-sdk/audiences/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/retrier.go rename to seed/go-sdk/audiences/core/retrier.go diff --git a/seed/go-sdk/audiences/core/stringer.go b/seed/go-sdk/audiences/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/audiences/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/audiences/core/time.go b/seed/go-sdk/audiences/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/audiences/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/audiences/foldera/client/client.go b/seed/go-sdk/audiences/foldera/client/client.go new file mode 100644 index 00000000000..849491250b2 --- /dev/null +++ b/seed/go-sdk/audiences/foldera/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/audiences/fern/core" + service "github.com/audiences/fern/foldera/service" + option "github.com/audiences/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/audiences/foldera/service.go b/seed/go-sdk/audiences/foldera/service.go new file mode 100644 index 00000000000..e550c739f33 --- /dev/null +++ b/seed/go-sdk/audiences/foldera/service.go @@ -0,0 +1,39 @@ +// This file was auto-generated by Fern from our API Definition. + +package foldera + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/audiences/fern/core" + folderb "github.com/audiences/fern/folderb" +) + +type Response struct { + Foo *folderb.Foo `json:"foo,omitempty" url:"foo,omitempty"` + + _rawJSON json.RawMessage +} + +func (r *Response) UnmarshalJSON(data []byte) error { + type unmarshaler Response + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = Response(value) + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *Response) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} diff --git a/seed/go-sdk/audiences/foldera/service/client.go b/seed/go-sdk/audiences/foldera/service/client.go new file mode 100644 index 00000000000..938dce8fb6a --- /dev/null +++ b/seed/go-sdk/audiences/foldera/service/client.go @@ -0,0 +1,65 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + core "github.com/audiences/fern/core" + foldera "github.com/audiences/fern/foldera" + option "github.com/audiences/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetDirectThread( + ctx context.Context, + opts ...option.RequestOption, +) (*foldera.Response, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *foldera.Response + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/audiences/folderb/types.go b/seed/go-sdk/audiences/folderb/types.go new file mode 100644 index 00000000000..cca3bf5f23e --- /dev/null +++ b/seed/go-sdk/audiences/folderb/types.go @@ -0,0 +1,39 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderb + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/audiences/fern/core" + folderc "github.com/audiences/fern/folderc" +) + +type Foo struct { + Foo *folderc.Foo `json:"foo,omitempty" url:"foo,omitempty"` + + _rawJSON json.RawMessage +} + +func (f *Foo) UnmarshalJSON(data []byte) error { + type unmarshaler Foo + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = Foo(value) + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *Foo) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-sdk/audiences/folderc/types.go b/seed/go-sdk/audiences/folderc/types.go new file mode 100644 index 00000000000..02d4eb29d41 --- /dev/null +++ b/seed/go-sdk/audiences/folderc/types.go @@ -0,0 +1,39 @@ +// This file was auto-generated by Fern from our API Definition. + +package folderc + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/audiences/fern/core" + uuid "github.com/google/uuid" +) + +type Foo struct { + BarProperty uuid.UUID `json:"bar_property" url:"bar_property"` + + _rawJSON json.RawMessage +} + +func (f *Foo) UnmarshalJSON(data []byte) error { + type unmarshaler Foo + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = Foo(value) + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *Foo) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-sdk/audiences/foo.go b/seed/go-sdk/audiences/foo.go new file mode 100644 index 00000000000..4ae009c9789 --- /dev/null +++ b/seed/go-sdk/audiences/foo.go @@ -0,0 +1,46 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type FindRequest struct { + OptionalString OptionalString `json:"-" url:"optionalString,omitempty"` + PublicProperty *string `json:"publicProperty,omitempty" url:"publicProperty,omitempty"` + PrivateProperty *int `json:"privateProperty,omitempty" url:"privateProperty,omitempty"` +} + +type ImportingType struct { + Imported Imported `json:"imported" url:"imported"` + + _rawJSON json.RawMessage +} + +func (i *ImportingType) UnmarshalJSON(data []byte) error { + type unmarshaler ImportingType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = ImportingType(value) + i._rawJSON = json.RawMessage(data) + return nil +} + +func (i *ImportingType) String() string { + if len(i._rawJSON) > 0 { + if value, err := core.StringifyJSON(i._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type OptionalString = *string diff --git a/seed/go-sdk/audiences/foo/client.go b/seed/go-sdk/audiences/foo/client.go new file mode 100644 index 00000000000..d23af4a927d --- /dev/null +++ b/seed/go-sdk/audiences/foo/client.go @@ -0,0 +1,75 @@ +// This file was auto-generated by Fern from our API Definition. + +package foo + +import ( + context "context" + fern "github.com/audiences/fern" + core "github.com/audiences/fern/core" + option "github.com/audiences/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Find( + ctx context.Context, + request *fern.FindRequest, + opts ...option.RequestOption, +) (*fern.ImportingType, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.ImportingType + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/audiences/go.mod b/seed/go-sdk/audiences/go.mod new file mode 100644 index 00000000000..1e591b66fd0 --- /dev/null +++ b/seed/go-sdk/audiences/go.mod @@ -0,0 +1,9 @@ +module github.com/audiences/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/file-upload/go.sum b/seed/go-sdk/audiences/go.sum similarity index 100% rename from generators/go/seed/sdk/file-upload/go.sum rename to seed/go-sdk/audiences/go.sum diff --git a/seed/go-sdk/audiences/option/request_option.go b/seed/go-sdk/audiences/option/request_option.go new file mode 100644 index 00000000000..e356ed2f499 --- /dev/null +++ b/seed/go-sdk/audiences/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/audiences/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/audiences/pointer.go b/seed/go-sdk/audiences/pointer.go new file mode 100644 index 00000000000..a2fbf4413dc --- /dev/null +++ b/seed/go-sdk/audiences/pointer.go @@ -0,0 +1,132 @@ +package audiences + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/audiences/snippet.json b/seed/go-sdk/audiences/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/audiences/types.go b/seed/go-sdk/audiences/types.go new file mode 100644 index 00000000000..2e9cf7fea4e --- /dev/null +++ b/seed/go-sdk/audiences/types.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package audiences + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/audiences/fern/core" +) + +type Imported = string + +type FilteredType struct { + PublicProperty *string `json:"public_property,omitempty" url:"public_property,omitempty"` + PrivateProperty int `json:"private_property" url:"private_property"` + + _rawJSON json.RawMessage +} + +func (f *FilteredType) UnmarshalJSON(data []byte) error { + type unmarshaler FilteredType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = FilteredType(value) + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *FilteredType) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} diff --git a/seed/go-sdk/auth-environment-variables/.github/workflows/ci.yml b/seed/go-sdk/auth-environment-variables/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/auth-environment-variables/client/client.go b/seed/go-sdk/auth-environment-variables/client/client.go new file mode 100644 index 00000000000..9110c9cc4b8 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/auth-environment-variables/fern/core" + option "github.com/auth-environment-variables/fern/option" + service "github.com/auth-environment-variables/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/auth-environment-variables/client/client_test.go b/seed/go-sdk/auth-environment-variables/client/client_test.go new file mode 100644 index 00000000000..c7bdea85c4d --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/auth-environment-variables/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/idempotency-headers/core/core.go b/seed/go-sdk/auth-environment-variables/core/core.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/core.go rename to seed/go-sdk/auth-environment-variables/core/core.go diff --git a/generators/go/seed/sdk/idempotency-headers/core/core_test.go b/seed/go-sdk/auth-environment-variables/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/core_test.go rename to seed/go-sdk/auth-environment-variables/core/core_test.go diff --git a/generators/go/seed/sdk/idempotency-headers/core/query.go b/seed/go-sdk/auth-environment-variables/core/query.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/query.go rename to seed/go-sdk/auth-environment-variables/core/query.go diff --git a/generators/go/seed/sdk/idempotency-headers/core/query_test.go b/seed/go-sdk/auth-environment-variables/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/query_test.go rename to seed/go-sdk/auth-environment-variables/core/query_test.go diff --git a/seed/go-sdk/auth-environment-variables/core/request_option.go b/seed/go-sdk/auth-environment-variables/core/request_option.go new file mode 100644 index 00000000000..ff9ad16dd7f --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/core/request_option.go @@ -0,0 +1,100 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + fmt "fmt" + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + ApiKey string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + header.Set("X-FERN-API-KEY", fmt.Sprintf("%v", r.ApiKey)) + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/auth-environment-variables/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// ApiKeyOption implements the RequestOption interface. +type ApiKeyOption struct { + ApiKey string +} + +func (a *ApiKeyOption) applyRequestOptions(opts *RequestOptions) { + opts.ApiKey = a.ApiKey +} diff --git a/generators/go/seed/sdk/idempotency-headers/core/retrier.go b/seed/go-sdk/auth-environment-variables/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/retrier.go rename to seed/go-sdk/auth-environment-variables/core/retrier.go diff --git a/seed/go-sdk/auth-environment-variables/core/stringer.go b/seed/go-sdk/auth-environment-variables/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/auth-environment-variables/core/time.go b/seed/go-sdk/auth-environment-variables/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/auth-environment-variables/go.mod b/seed/go-sdk/auth-environment-variables/go.mod new file mode 100644 index 00000000000..2f13f6e8670 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/go.mod @@ -0,0 +1,9 @@ +module github.com/auth-environment-variables/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/idempotency-headers/go.sum b/seed/go-sdk/auth-environment-variables/go.sum similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/go.sum rename to seed/go-sdk/auth-environment-variables/go.sum diff --git a/seed/go-sdk/auth-environment-variables/option/request_option.go b/seed/go-sdk/auth-environment-variables/option/request_option.go new file mode 100644 index 00000000000..b9e0bd6dbe0 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/auth-environment-variables/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithApiKey sets the apiKey auth request header. +func WithApiKey(apiKey string) *core.ApiKeyOption { + return &core.ApiKeyOption{ + ApiKey: apiKey, + } +} diff --git a/seed/go-sdk/auth-environment-variables/pointer.go b/seed/go-sdk/auth-environment-variables/pointer.go new file mode 100644 index 00000000000..3d9b1d6cf0d --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/pointer.go @@ -0,0 +1,132 @@ +package authenvironmentvariables + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/auth-environment-variables/service/client.go b/seed/go-sdk/auth-environment-variables/service/client.go new file mode 100644 index 00000000000..fc94c56d487 --- /dev/null +++ b/seed/go-sdk/auth-environment-variables/service/client.go @@ -0,0 +1,65 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + core "github.com/auth-environment-variables/fern/core" + option "github.com/auth-environment-variables/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// GET request with custom api key +func (c *Client) GetWithApiKey( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "apiKey" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/auth-environment-variables/snippet.json b/seed/go-sdk/auth-environment-variables/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/basic-auth/.github/workflows/ci.yml b/seed/go-sdk/basic-auth/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/basic-auth/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/basic-auth/basicauth/client.go b/seed/go-sdk/basic-auth/basicauth/client.go new file mode 100644 index 00000000000..ee1f71c609c --- /dev/null +++ b/seed/go-sdk/basic-auth/basicauth/client.go @@ -0,0 +1,154 @@ +// This file was auto-generated by Fern from our API Definition. + +package basicauth + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fern "github.com/basic-auth/fern" + core "github.com/basic-auth/fern/core" + option "github.com/basic-auth/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// GET request with basic auth scheme +func (c *Client) GetWithBasicAuth( + ctx context.Context, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "basic-auth" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 401: + value := new(fern.UnauthorizedRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return false, err + } + return response, nil +} + +// POST request with basic auth scheme +func (c *Client) PostWithBasicAuth( + ctx context.Context, + request interface{}, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "basic-auth" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 401: + value := new(fern.UnauthorizedRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + case 400: + value := new(fern.BadRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return false, err + } + return response, nil +} diff --git a/seed/go-sdk/basic-auth/client/client.go b/seed/go-sdk/basic-auth/client/client.go new file mode 100644 index 00000000000..4fa2f99f2fd --- /dev/null +++ b/seed/go-sdk/basic-auth/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + basicauth "github.com/basic-auth/fern/basicauth" + core "github.com/basic-auth/fern/core" + option "github.com/basic-auth/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + BasicAuth *basicauth.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + BasicAuth: basicauth.NewClient(opts...), + } +} diff --git a/seed/go-sdk/basic-auth/client/client_test.go b/seed/go-sdk/basic-auth/client/client_test.go new file mode 100644 index 00000000000..3d58ac5b0e8 --- /dev/null +++ b/seed/go-sdk/basic-auth/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/basic-auth/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/literal-headers/core/core.go b/seed/go-sdk/basic-auth/core/core.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/core.go rename to seed/go-sdk/basic-auth/core/core.go diff --git a/generators/go/seed/sdk/literal-headers/core/core_test.go b/seed/go-sdk/basic-auth/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/core_test.go rename to seed/go-sdk/basic-auth/core/core_test.go diff --git a/generators/go/seed/sdk/literal-headers/core/query.go b/seed/go-sdk/basic-auth/core/query.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/query.go rename to seed/go-sdk/basic-auth/core/query.go diff --git a/generators/go/seed/sdk/literal-headers/core/query_test.go b/seed/go-sdk/basic-auth/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/query_test.go rename to seed/go-sdk/basic-auth/core/query_test.go diff --git a/seed/go-sdk/basic-auth/core/request_option.go b/seed/go-sdk/basic-auth/core/request_option.go new file mode 100644 index 00000000000..707cd47b3b4 --- /dev/null +++ b/seed/go-sdk/basic-auth/core/request_option.go @@ -0,0 +1,105 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + base64 "encoding/base64" + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Username string + Password string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Username != "" && r.Password != "" { + header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(r.Username+": "+r.Password))) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/basic-auth/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// BasicAuthOption implements the RequestOption interface. +type BasicAuthOption struct { + Username string + Password string +} + +func (b *BasicAuthOption) applyRequestOptions(opts *RequestOptions) { + opts.Username = b.Username + opts.Password = b.Password +} diff --git a/generators/go/seed/sdk/literal-headers/core/retrier.go b/seed/go-sdk/basic-auth/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/retrier.go rename to seed/go-sdk/basic-auth/core/retrier.go diff --git a/seed/go-sdk/basic-auth/core/stringer.go b/seed/go-sdk/basic-auth/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/basic-auth/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/basic-auth/core/time.go b/seed/go-sdk/basic-auth/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/basic-auth/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/basic-auth/errors.go b/seed/go-sdk/basic-auth/errors.go new file mode 100644 index 00000000000..be7566b4a5b --- /dev/null +++ b/seed/go-sdk/basic-auth/errors.go @@ -0,0 +1,44 @@ +// This file was auto-generated by Fern from our API Definition. + +package basicauth + +import ( + json "encoding/json" + core "github.com/basic-auth/fern/core" +) + +type BadRequest struct { + *core.APIError +} + +func (b *BadRequest) UnmarshalJSON(data []byte) error { + b.StatusCode = 400 + return nil +} + +func (b *BadRequest) MarshalJSON() ([]byte, error) { + return nil, nil +} + +type UnauthorizedRequest struct { + *core.APIError + Body *UnauthorizedRequestErrorBody +} + +func (u *UnauthorizedRequest) UnmarshalJSON(data []byte) error { + var body *UnauthorizedRequestErrorBody + if err := json.Unmarshal(data, &body); err != nil { + return err + } + u.StatusCode = 401 + u.Body = body + return nil +} + +func (u *UnauthorizedRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(u.Body) +} + +func (u *UnauthorizedRequest) Unwrap() error { + return u.APIError +} diff --git a/seed/go-sdk/basic-auth/go.mod b/seed/go-sdk/basic-auth/go.mod new file mode 100644 index 00000000000..42113751bc3 --- /dev/null +++ b/seed/go-sdk/basic-auth/go.mod @@ -0,0 +1,9 @@ +module github.com/basic-auth/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/literal-headers/go.sum b/seed/go-sdk/basic-auth/go.sum similarity index 100% rename from generators/go/seed/sdk/literal-headers/go.sum rename to seed/go-sdk/basic-auth/go.sum diff --git a/seed/go-sdk/basic-auth/option/request_option.go b/seed/go-sdk/basic-auth/option/request_option.go new file mode 100644 index 00000000000..8db27027807 --- /dev/null +++ b/seed/go-sdk/basic-auth/option/request_option.go @@ -0,0 +1,49 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/basic-auth/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithBasicAuth sets the 'Authorization: Basic ' request header. +func WithBasicAuth(username, password string) *core.BasicAuthOption { + return &core.BasicAuthOption{ + Username: username, + Password: password, + } +} diff --git a/seed/go-sdk/basic-auth/pointer.go b/seed/go-sdk/basic-auth/pointer.go new file mode 100644 index 00000000000..9f10e872e83 --- /dev/null +++ b/seed/go-sdk/basic-auth/pointer.go @@ -0,0 +1,132 @@ +package basicauth + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/basic-auth/snippet.json b/seed/go-sdk/basic-auth/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/basic-auth/types.go b/seed/go-sdk/basic-auth/types.go new file mode 100644 index 00000000000..c5d024c1604 --- /dev/null +++ b/seed/go-sdk/basic-auth/types.go @@ -0,0 +1,38 @@ +// This file was auto-generated by Fern from our API Definition. + +package basicauth + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/basic-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` + + _rawJSON json.RawMessage +} + +func (u *UnauthorizedRequestErrorBody) UnmarshalJSON(data []byte) error { + type unmarshaler UnauthorizedRequestErrorBody + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UnauthorizedRequestErrorBody(value) + u._rawJSON = json.RawMessage(data) + return nil +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if len(u._rawJSON) > 0 { + if value, err := core.StringifyJSON(u._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/bearer-token-environment-variable/.github/workflows/ci.yml b/seed/go-sdk/bearer-token-environment-variable/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/bearer-token-environment-variable/client/client.go b/seed/go-sdk/bearer-token-environment-variable/client/client.go new file mode 100644 index 00000000000..c41ee177487 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/bearer-token-environment-variable/fern/core" + option "github.com/bearer-token-environment-variable/fern/option" + service "github.com/bearer-token-environment-variable/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/bearer-token-environment-variable/client/client_test.go b/seed/go-sdk/bearer-token-environment-variable/client/client_test.go new file mode 100644 index 00000000000..e6f45166b98 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/bearer-token-environment-variable/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/literal/core/core.go b/seed/go-sdk/bearer-token-environment-variable/core/core.go similarity index 100% rename from generators/go/seed/sdk/literal/core/core.go rename to seed/go-sdk/bearer-token-environment-variable/core/core.go diff --git a/generators/go/seed/sdk/literal/core/core_test.go b/seed/go-sdk/bearer-token-environment-variable/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/literal/core/core_test.go rename to seed/go-sdk/bearer-token-environment-variable/core/core_test.go diff --git a/generators/go/seed/sdk/literal/core/query.go b/seed/go-sdk/bearer-token-environment-variable/core/query.go similarity index 100% rename from generators/go/seed/sdk/literal/core/query.go rename to seed/go-sdk/bearer-token-environment-variable/core/query.go diff --git a/generators/go/seed/sdk/literal/core/query_test.go b/seed/go-sdk/bearer-token-environment-variable/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/literal/core/query_test.go rename to seed/go-sdk/bearer-token-environment-variable/core/query_test.go diff --git a/seed/go-sdk/bearer-token-environment-variable/core/request_option.go b/seed/go-sdk/bearer-token-environment-variable/core/request_option.go new file mode 100644 index 00000000000..c109b7e35d0 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + ApiKey string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.ApiKey != "" { + header.Set("Authorization", "Bearer "+r.ApiKey) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/bearer-token-environment-variable/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// ApiKeyOption implements the RequestOption interface. +type ApiKeyOption struct { + ApiKey string +} + +func (a *ApiKeyOption) applyRequestOptions(opts *RequestOptions) { + opts.ApiKey = a.ApiKey +} diff --git a/generators/go/seed/sdk/literal/core/retrier.go b/seed/go-sdk/bearer-token-environment-variable/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/literal/core/retrier.go rename to seed/go-sdk/bearer-token-environment-variable/core/retrier.go diff --git a/seed/go-sdk/bearer-token-environment-variable/core/stringer.go b/seed/go-sdk/bearer-token-environment-variable/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/bearer-token-environment-variable/core/time.go b/seed/go-sdk/bearer-token-environment-variable/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/bearer-token-environment-variable/go.mod b/seed/go-sdk/bearer-token-environment-variable/go.mod new file mode 100644 index 00000000000..b36b6ad633b --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/go.mod @@ -0,0 +1,9 @@ +module github.com/bearer-token-environment-variable/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/literal/go.sum b/seed/go-sdk/bearer-token-environment-variable/go.sum similarity index 100% rename from generators/go/seed/sdk/literal/go.sum rename to seed/go-sdk/bearer-token-environment-variable/go.sum diff --git a/seed/go-sdk/bearer-token-environment-variable/option/request_option.go b/seed/go-sdk/bearer-token-environment-variable/option/request_option.go new file mode 100644 index 00000000000..84a7cfcd654 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/bearer-token-environment-variable/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithApiKey sets the 'Authorization: Bearer ' request header. +func WithApiKey(apiKey string) *core.ApiKeyOption { + return &core.ApiKeyOption{ + ApiKey: apiKey, + } +} diff --git a/seed/go-sdk/bearer-token-environment-variable/pointer.go b/seed/go-sdk/bearer-token-environment-variable/pointer.go new file mode 100644 index 00000000000..29aee1d56f0 --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/pointer.go @@ -0,0 +1,132 @@ +package bearertokenenvironmentvariable + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/bearer-token-environment-variable/service/client.go b/seed/go-sdk/bearer-token-environment-variable/service/client.go new file mode 100644 index 00000000000..0f7769ea0ff --- /dev/null +++ b/seed/go-sdk/bearer-token-environment-variable/service/client.go @@ -0,0 +1,65 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + core "github.com/bearer-token-environment-variable/fern/core" + option "github.com/bearer-token-environment-variable/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// GET request with custom api key +func (c *Client) GetWithBearerToken( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "apiKey" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/bearer-token-environment-variable/snippet.json b/seed/go-sdk/bearer-token-environment-variable/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/bytes/.github/workflows/ci.yml b/seed/go-sdk/bytes/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/bytes/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/bytes/client/client.go b/seed/go-sdk/bytes/client/client.go similarity index 100% rename from generators/go/seed/sdk/bytes/client/client.go rename to seed/go-sdk/bytes/client/client.go diff --git a/generators/go/seed/sdk/bytes/client/client_test.go b/seed/go-sdk/bytes/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/bytes/client/client_test.go rename to seed/go-sdk/bytes/client/client_test.go diff --git a/generators/go/seed/sdk/plain-text/core/core.go b/seed/go-sdk/bytes/core/core.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/core.go rename to seed/go-sdk/bytes/core/core.go diff --git a/generators/go/seed/sdk/plain-text/core/core_test.go b/seed/go-sdk/bytes/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/core_test.go rename to seed/go-sdk/bytes/core/core_test.go diff --git a/generators/go/seed/sdk/plain-text/core/query.go b/seed/go-sdk/bytes/core/query.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/query.go rename to seed/go-sdk/bytes/core/query.go diff --git a/generators/go/seed/sdk/plain-text/core/query_test.go b/seed/go-sdk/bytes/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/query_test.go rename to seed/go-sdk/bytes/core/query_test.go diff --git a/generators/go/seed/sdk/bytes/core/request_option.go b/seed/go-sdk/bytes/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/bytes/core/request_option.go rename to seed/go-sdk/bytes/core/request_option.go diff --git a/generators/go/seed/sdk/plain-text/core/retrier.go b/seed/go-sdk/bytes/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/retrier.go rename to seed/go-sdk/bytes/core/retrier.go diff --git a/seed/go-sdk/bytes/core/stringer.go b/seed/go-sdk/bytes/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/bytes/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/bytes/core/time.go b/seed/go-sdk/bytes/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/bytes/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/bytes/go.mod b/seed/go-sdk/bytes/go.mod similarity index 100% rename from generators/go/seed/sdk/bytes/go.mod rename to seed/go-sdk/bytes/go.mod diff --git a/generators/go/seed/sdk/plain-text/go.sum b/seed/go-sdk/bytes/go.sum similarity index 100% rename from generators/go/seed/sdk/plain-text/go.sum rename to seed/go-sdk/bytes/go.sum diff --git a/generators/go/seed/sdk/bytes/option/request_option.go b/seed/go-sdk/bytes/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/bytes/option/request_option.go rename to seed/go-sdk/bytes/option/request_option.go diff --git a/generators/go/seed/sdk/bytes/pointer.go b/seed/go-sdk/bytes/pointer.go similarity index 75% rename from generators/go/seed/sdk/bytes/pointer.go rename to seed/go-sdk/bytes/pointer.go index 57c1a6b359a..87bec1d34f1 100644 --- a/generators/go/seed/sdk/bytes/pointer.go +++ b/seed/go-sdk/bytes/pointer.go @@ -1,6 +1,10 @@ package bytes -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/seed/sdk/bytes/service/client.go b/seed/go-sdk/bytes/service/client.go similarity index 100% rename from generators/go/seed/sdk/bytes/service/client.go rename to seed/go-sdk/bytes/service/client.go diff --git a/seed/go-sdk/bytes/snippet.json b/seed/go-sdk/bytes/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/circular-references/.github/workflows/ci.yml b/seed/go-sdk/circular-references/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/circular-references/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/circular-references/client/client.go b/seed/go-sdk/circular-references/client/client.go new file mode 100644 index 00000000000..e8979fb6476 --- /dev/null +++ b/seed/go-sdk/circular-references/client/client.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/circular-references/fern/core" + option "github.com/circular-references/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} diff --git a/seed/go-sdk/circular-references/client/client_test.go b/seed/go-sdk/circular-references/client/client_test.go new file mode 100644 index 00000000000..700a697f1fe --- /dev/null +++ b/seed/go-sdk/circular-references/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/circular-references/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/query-parameters/core/core.go b/seed/go-sdk/circular-references/core/core.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/core.go rename to seed/go-sdk/circular-references/core/core.go diff --git a/generators/go/seed/sdk/query-parameters/core/core_test.go b/seed/go-sdk/circular-references/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/core_test.go rename to seed/go-sdk/circular-references/core/core_test.go diff --git a/generators/go/seed/sdk/query-parameters/core/query.go b/seed/go-sdk/circular-references/core/query.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/query.go rename to seed/go-sdk/circular-references/core/query.go diff --git a/generators/go/seed/sdk/query-parameters/core/query_test.go b/seed/go-sdk/circular-references/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/query_test.go rename to seed/go-sdk/circular-references/core/query_test.go diff --git a/seed/go-sdk/circular-references/core/request_option.go b/seed/go-sdk/circular-references/core/request_option.go new file mode 100644 index 00000000000..9b743a3e697 --- /dev/null +++ b/seed/go-sdk/circular-references/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/circular-references/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/generators/go/seed/sdk/query-parameters/core/retrier.go b/seed/go-sdk/circular-references/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/retrier.go rename to seed/go-sdk/circular-references/core/retrier.go diff --git a/seed/go-sdk/circular-references/core/stringer.go b/seed/go-sdk/circular-references/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/circular-references/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/circular-references/core/time.go b/seed/go-sdk/circular-references/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/circular-references/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/circular-references/go.mod b/seed/go-sdk/circular-references/go.mod new file mode 100644 index 00000000000..b4c8f988e03 --- /dev/null +++ b/seed/go-sdk/circular-references/go.mod @@ -0,0 +1,9 @@ +module github.com/circular-references/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/query-parameters/go.sum b/seed/go-sdk/circular-references/go.sum similarity index 100% rename from generators/go/seed/sdk/query-parameters/go.sum rename to seed/go-sdk/circular-references/go.sum diff --git a/seed/go-sdk/circular-references/option/request_option.go b/seed/go-sdk/circular-references/option/request_option.go new file mode 100644 index 00000000000..e70f7b91d4f --- /dev/null +++ b/seed/go-sdk/circular-references/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/circular-references/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/circular-references/pointer.go b/seed/go-sdk/circular-references/pointer.go new file mode 100644 index 00000000000..faaf462e6d0 --- /dev/null +++ b/seed/go-sdk/circular-references/pointer.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/circular-references/snippet.json b/seed/go-sdk/circular-references/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/circular-references/types.go b/seed/go-sdk/circular-references/types.go new file mode 100644 index 00000000000..ae35a3f4443 --- /dev/null +++ b/seed/go-sdk/circular-references/types.go @@ -0,0 +1,336 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/circular-references/fern/core" +) + +type ImportingA struct { + A *A `json:"a,omitempty" url:"a,omitempty"` + + _rawJSON json.RawMessage +} + +func (i *ImportingA) UnmarshalJSON(data []byte) error { + type unmarshaler ImportingA + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = ImportingA(value) + i._rawJSON = json.RawMessage(data) + return nil +} + +func (i *ImportingA) String() string { + if len(i._rawJSON) > 0 { + if value, err := core.StringifyJSON(i._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type RootType struct { + S string `json:"s" url:"s"` + + _rawJSON json.RawMessage +} + +func (r *RootType) UnmarshalJSON(data []byte) error { + type unmarshaler RootType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = RootType(value) + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *RootType) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type A struct { + S string `json:"s" url:"s"` + + _rawJSON json.RawMessage +} + +func (a *A) UnmarshalJSON(data []byte) error { + type unmarshaler A + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = A(value) + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *A) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type ContainerValue struct { + Type string + List []*FieldValue + Optional *FieldValue +} + +func NewContainerValueFromList(value []*FieldValue) *ContainerValue { + return &ContainerValue{Type: "list", List: value} +} + +func NewContainerValueFromOptional(value *FieldValue) *ContainerValue { + return &ContainerValue{Type: "optional", Optional: value} +} + +func (c *ContainerValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + c.Type = unmarshaler.Type + switch unmarshaler.Type { + case "list": + var valueUnmarshaler struct { + List []*FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.List = valueUnmarshaler.List + case "optional": + var valueUnmarshaler struct { + Optional *FieldValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + c.Optional = valueUnmarshaler.Optional + } + return nil +} + +func (c ContainerValue) MarshalJSON() ([]byte, error) { + switch c.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + var marshaler = struct { + Type string `json:"type"` + List []*FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + List: c.List, + } + return json.Marshal(marshaler) + case "optional": + var marshaler = struct { + Type string `json:"type"` + Optional *FieldValue `json:"value,omitempty"` + }{ + Type: c.Type, + Optional: c.Optional, + } + return json.Marshal(marshaler) + } +} + +type ContainerValueVisitor interface { + VisitList([]*FieldValue) error + VisitOptional(*FieldValue) error +} + +func (c *ContainerValue) Accept(visitor ContainerValueVisitor) error { + switch c.Type { + default: + return fmt.Errorf("invalid type %s in %T", c.Type, c) + case "list": + return visitor.VisitList(c.List) + case "optional": + return visitor.VisitOptional(c.Optional) + } +} + +type FieldValue struct { + Type string + PrimitiveValue PrimitiveValue + ObjectValue *ObjectValue + ContainerValue *ContainerValue +} + +func NewFieldValueFromPrimitiveValue(value PrimitiveValue) *FieldValue { + return &FieldValue{Type: "primitive_value", PrimitiveValue: value} +} + +func NewFieldValueFromObjectValue(value *ObjectValue) *FieldValue { + return &FieldValue{Type: "object_value", ObjectValue: value} +} + +func NewFieldValueFromContainerValue(value *ContainerValue) *FieldValue { + return &FieldValue{Type: "container_value", ContainerValue: value} +} + +func (f *FieldValue) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + f.Type = unmarshaler.Type + switch unmarshaler.Type { + case "primitive_value": + var valueUnmarshaler struct { + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.PrimitiveValue = valueUnmarshaler.PrimitiveValue + case "object_value": + value := new(ObjectValue) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + f.ObjectValue = value + case "container_value": + var valueUnmarshaler struct { + ContainerValue *ContainerValue `json:"value,omitempty"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + f.ContainerValue = valueUnmarshaler.ContainerValue + } + return nil +} + +func (f FieldValue) MarshalJSON() ([]byte, error) { + switch f.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + var marshaler = struct { + Type string `json:"type"` + PrimitiveValue PrimitiveValue `json:"value,omitempty"` + }{ + Type: f.Type, + PrimitiveValue: f.PrimitiveValue, + } + return json.Marshal(marshaler) + case "object_value": + var marshaler = struct { + Type string `json:"type"` + *ObjectValue + }{ + Type: f.Type, + ObjectValue: f.ObjectValue, + } + return json.Marshal(marshaler) + case "container_value": + var marshaler = struct { + Type string `json:"type"` + ContainerValue *ContainerValue `json:"value,omitempty"` + }{ + Type: f.Type, + ContainerValue: f.ContainerValue, + } + return json.Marshal(marshaler) + } +} + +type FieldValueVisitor interface { + VisitPrimitiveValue(PrimitiveValue) error + VisitObjectValue(*ObjectValue) error + VisitContainerValue(*ContainerValue) error +} + +func (f *FieldValue) Accept(visitor FieldValueVisitor) error { + switch f.Type { + default: + return fmt.Errorf("invalid type %s in %T", f.Type, f) + case "primitive_value": + return visitor.VisitPrimitiveValue(f.PrimitiveValue) + case "object_value": + return visitor.VisitObjectValue(f.ObjectValue) + case "container_value": + return visitor.VisitContainerValue(f.ContainerValue) + } +} + +type ObjectValue struct { + _rawJSON json.RawMessage +} + +func (o *ObjectValue) UnmarshalJSON(data []byte) error { + type unmarshaler ObjectValue + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *o = ObjectValue(value) + o._rawJSON = json.RawMessage(data) + return nil +} + +func (o *ObjectValue) String() string { + if len(o._rawJSON) > 0 { + if value, err := core.StringifyJSON(o._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(o); err == nil { + return value + } + return fmt.Sprintf("%#v", o) +} + +type PrimitiveValue string + +const ( + PrimitiveValueString PrimitiveValue = "STRING" + PrimitiveValueNumber PrimitiveValue = "NUMBER" +) + +func NewPrimitiveValueFromString(s string) (PrimitiveValue, error) { + switch s { + case "STRING": + return PrimitiveValueString, nil + case "NUMBER": + return PrimitiveValueNumber, nil + } + var t PrimitiveValue + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (p PrimitiveValue) Ptr() *PrimitiveValue { + return &p +} diff --git a/seed/go-sdk/custom-auth/.github/workflows/ci.yml b/seed/go-sdk/custom-auth/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/custom-auth/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/custom-auth/client/client.go b/seed/go-sdk/custom-auth/client/client.go new file mode 100644 index 00000000000..e0df460006c --- /dev/null +++ b/seed/go-sdk/custom-auth/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/custom-auth/fern/core" + customauth "github.com/custom-auth/fern/customauth" + option "github.com/custom-auth/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + CustomAuth *customauth.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + CustomAuth: customauth.NewClient(opts...), + } +} diff --git a/seed/go-sdk/custom-auth/client/client_test.go b/seed/go-sdk/custom-auth/client/client_test.go new file mode 100644 index 00000000000..7840581c76c --- /dev/null +++ b/seed/go-sdk/custom-auth/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/custom-auth/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/generators/go/seed/sdk/response-property/core/core.go b/seed/go-sdk/custom-auth/core/core.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/core.go rename to seed/go-sdk/custom-auth/core/core.go diff --git a/generators/go/seed/sdk/response-property/core/core_test.go b/seed/go-sdk/custom-auth/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/core_test.go rename to seed/go-sdk/custom-auth/core/core_test.go diff --git a/generators/go/seed/sdk/response-property/core/query.go b/seed/go-sdk/custom-auth/core/query.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/query.go rename to seed/go-sdk/custom-auth/core/query.go diff --git a/generators/go/seed/sdk/response-property/core/query_test.go b/seed/go-sdk/custom-auth/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/query_test.go rename to seed/go-sdk/custom-auth/core/query_test.go diff --git a/seed/go-sdk/custom-auth/core/request_option.go b/seed/go-sdk/custom-auth/core/request_option.go new file mode 100644 index 00000000000..a7394e5dc76 --- /dev/null +++ b/seed/go-sdk/custom-auth/core/request_option.go @@ -0,0 +1,100 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + fmt "fmt" + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + CustomAuthScheme string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + header.Set("X-API-KEY", fmt.Sprintf("%v", r.CustomAuthScheme)) + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/custom-auth/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// CustomAuthSchemeOption implements the RequestOption interface. +type CustomAuthSchemeOption struct { + CustomAuthScheme string +} + +func (c *CustomAuthSchemeOption) applyRequestOptions(opts *RequestOptions) { + opts.CustomAuthScheme = c.CustomAuthScheme +} diff --git a/generators/go/seed/sdk/response-property/core/retrier.go b/seed/go-sdk/custom-auth/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/retrier.go rename to seed/go-sdk/custom-auth/core/retrier.go diff --git a/seed/go-sdk/custom-auth/core/stringer.go b/seed/go-sdk/custom-auth/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/custom-auth/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/custom-auth/core/time.go b/seed/go-sdk/custom-auth/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/custom-auth/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/custom-auth/customauth/client.go b/seed/go-sdk/custom-auth/customauth/client.go new file mode 100644 index 00000000000..bf4a7bc4b9e --- /dev/null +++ b/seed/go-sdk/custom-auth/customauth/client.go @@ -0,0 +1,154 @@ +// This file was auto-generated by Fern from our API Definition. + +package customauth + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fern "github.com/custom-auth/fern" + core "github.com/custom-auth/fern/core" + option "github.com/custom-auth/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// GET request with custom auth scheme +func (c *Client) GetWithCustomAuth( + ctx context.Context, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "custom-auth" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 401: + value := new(fern.UnauthorizedRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return false, err + } + return response, nil +} + +// POST request with custom auth scheme +func (c *Client) PostWithCustomAuth( + ctx context.Context, + request interface{}, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "custom-auth" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 401: + value := new(fern.UnauthorizedRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + case 400: + value := new(fern.BadRequest) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return false, err + } + return response, nil +} diff --git a/seed/go-sdk/custom-auth/errors.go b/seed/go-sdk/custom-auth/errors.go new file mode 100644 index 00000000000..82166d4bbc1 --- /dev/null +++ b/seed/go-sdk/custom-auth/errors.go @@ -0,0 +1,44 @@ +// This file was auto-generated by Fern from our API Definition. + +package customauth + +import ( + json "encoding/json" + core "github.com/custom-auth/fern/core" +) + +type BadRequest struct { + *core.APIError +} + +func (b *BadRequest) UnmarshalJSON(data []byte) error { + b.StatusCode = 400 + return nil +} + +func (b *BadRequest) MarshalJSON() ([]byte, error) { + return nil, nil +} + +type UnauthorizedRequest struct { + *core.APIError + Body *UnauthorizedRequestErrorBody +} + +func (u *UnauthorizedRequest) UnmarshalJSON(data []byte) error { + var body *UnauthorizedRequestErrorBody + if err := json.Unmarshal(data, &body); err != nil { + return err + } + u.StatusCode = 401 + u.Body = body + return nil +} + +func (u *UnauthorizedRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(u.Body) +} + +func (u *UnauthorizedRequest) Unwrap() error { + return u.APIError +} diff --git a/seed/go-sdk/custom-auth/go.mod b/seed/go-sdk/custom-auth/go.mod new file mode 100644 index 00000000000..8db6373836e --- /dev/null +++ b/seed/go-sdk/custom-auth/go.mod @@ -0,0 +1,9 @@ +module github.com/custom-auth/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/generators/go/seed/sdk/response-property/go.sum b/seed/go-sdk/custom-auth/go.sum similarity index 100% rename from generators/go/seed/sdk/response-property/go.sum rename to seed/go-sdk/custom-auth/go.sum diff --git a/seed/go-sdk/custom-auth/option/request_option.go b/seed/go-sdk/custom-auth/option/request_option.go new file mode 100644 index 00000000000..475eeabb9dd --- /dev/null +++ b/seed/go-sdk/custom-auth/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/custom-auth/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithCustomAuthScheme sets the customAuthScheme auth request header. +func WithCustomAuthScheme(customAuthScheme string) *core.CustomAuthSchemeOption { + return &core.CustomAuthSchemeOption{ + CustomAuthScheme: customAuthScheme, + } +} diff --git a/seed/go-sdk/custom-auth/pointer.go b/seed/go-sdk/custom-auth/pointer.go new file mode 100644 index 00000000000..39144936016 --- /dev/null +++ b/seed/go-sdk/custom-auth/pointer.go @@ -0,0 +1,132 @@ +package customauth + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/custom-auth/snippet.json b/seed/go-sdk/custom-auth/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/custom-auth/types.go b/seed/go-sdk/custom-auth/types.go new file mode 100644 index 00000000000..c07411437db --- /dev/null +++ b/seed/go-sdk/custom-auth/types.go @@ -0,0 +1,38 @@ +// This file was auto-generated by Fern from our API Definition. + +package customauth + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/custom-auth/fern/core" +) + +type UnauthorizedRequestErrorBody struct { + Message string `json:"message" url:"message"` + + _rawJSON json.RawMessage +} + +func (u *UnauthorizedRequestErrorBody) UnmarshalJSON(data []byte) error { + type unmarshaler UnauthorizedRequestErrorBody + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UnauthorizedRequestErrorBody(value) + u._rawJSON = json.RawMessage(data) + return nil +} + +func (u *UnauthorizedRequestErrorBody) String() string { + if len(u._rawJSON) > 0 { + if value, err := core.StringifyJSON(u._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/enum/.github/workflows/ci.yml b/seed/go-sdk/enum/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/enum/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/enum/client/client.go b/seed/go-sdk/enum/client/client.go similarity index 100% rename from generators/go/seed/sdk/enum/client/client.go rename to seed/go-sdk/enum/client/client.go diff --git a/generators/go/seed/sdk/enum/client/client_test.go b/seed/go-sdk/enum/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/enum/client/client_test.go rename to seed/go-sdk/enum/client/client_test.go diff --git a/generators/go/seed/sdk/streaming/core/core.go b/seed/go-sdk/enum/core/core.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/core.go rename to seed/go-sdk/enum/core/core.go diff --git a/generators/go/seed/sdk/streaming/core/core_test.go b/seed/go-sdk/enum/core/core_test.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/core_test.go rename to seed/go-sdk/enum/core/core_test.go diff --git a/generators/go/seed/sdk/streaming/core/query.go b/seed/go-sdk/enum/core/query.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/query.go rename to seed/go-sdk/enum/core/query.go diff --git a/generators/go/seed/sdk/streaming/core/query_test.go b/seed/go-sdk/enum/core/query_test.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/query_test.go rename to seed/go-sdk/enum/core/query_test.go diff --git a/generators/go/seed/sdk/enum/core/request_option.go b/seed/go-sdk/enum/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/enum/core/request_option.go rename to seed/go-sdk/enum/core/request_option.go diff --git a/generators/go/seed/sdk/streaming/core/retrier.go b/seed/go-sdk/enum/core/retrier.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/retrier.go rename to seed/go-sdk/enum/core/retrier.go diff --git a/seed/go-sdk/enum/core/stringer.go b/seed/go-sdk/enum/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/enum/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/enum/core/time.go b/seed/go-sdk/enum/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/enum/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/enum/go.mod b/seed/go-sdk/enum/go.mod similarity index 100% rename from generators/go/seed/sdk/enum/go.mod rename to seed/go-sdk/enum/go.mod diff --git a/generators/go/seed/sdk/streaming/go.sum b/seed/go-sdk/enum/go.sum similarity index 100% rename from generators/go/seed/sdk/streaming/go.sum rename to seed/go-sdk/enum/go.sum diff --git a/seed/go-sdk/enum/inlined_request.go b/seed/go-sdk/enum/inlined_request.go new file mode 100644 index 00000000000..da27590d949 --- /dev/null +++ b/seed/go-sdk/enum/inlined_request.go @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum + +type SendEnumInlinedRequest struct { + Operand *Operand `json:"operand,omitempty" url:"operand,omitempty"` +} diff --git a/generators/go/seed/sdk/enum/inlinedrequest/client.go b/seed/go-sdk/enum/inlinedrequest/client.go similarity index 96% rename from generators/go/seed/sdk/enum/inlinedrequest/client.go rename to seed/go-sdk/enum/inlinedrequest/client.go index cf7c7a8a9d8..0ba669cac9b 100644 --- a/generators/go/seed/sdk/enum/inlinedrequest/client.go +++ b/seed/go-sdk/enum/inlinedrequest/client.go @@ -44,7 +44,7 @@ func (c *Client) Send( if options.BaseURL != "" { baseURL = options.BaseURL } - endpointURL := baseURL + "/" + "inlined-request" + endpointURL := baseURL + "/" + "inlined" headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) diff --git a/generators/go/seed/sdk/enum/option/request_option.go b/seed/go-sdk/enum/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/enum/option/request_option.go rename to seed/go-sdk/enum/option/request_option.go diff --git a/generators/go/seed/sdk/enum/pathparam/client.go b/seed/go-sdk/enum/pathparam/client.go similarity index 93% rename from generators/go/seed/sdk/enum/pathparam/client.go rename to seed/go-sdk/enum/pathparam/client.go index f36d2a88892..5d422431d2e 100644 --- a/generators/go/seed/sdk/enum/pathparam/client.go +++ b/seed/go-sdk/enum/pathparam/client.go @@ -33,7 +33,7 @@ func NewClient(opts ...option.RequestOption) *Client { func (c *Client) Send( ctx context.Context, - value fern.Operand, + operand fern.Operand, opts ...option.RequestOption, ) error { options := core.NewRequestOptions(opts...) @@ -45,7 +45,7 @@ func (c *Client) Send( if options.BaseURL != "" { baseURL = options.BaseURL } - endpointURL := fmt.Sprintf(baseURL+"/"+"path-param/%v", value) + endpointURL := fmt.Sprintf(baseURL+"/"+"path/%v", operand) headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) diff --git a/generators/go/seed/sdk/enum/pointer.go b/seed/go-sdk/enum/pointer.go similarity index 75% rename from generators/go/seed/sdk/enum/pointer.go rename to seed/go-sdk/enum/pointer.go index 55e85741886..bc36d725234 100644 --- a/generators/go/seed/sdk/enum/pointer.go +++ b/seed/go-sdk/enum/pointer.go @@ -1,6 +1,10 @@ package enum -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/enum/query_param.go b/seed/go-sdk/enum/query_param.go new file mode 100644 index 00000000000..77a52be50b9 --- /dev/null +++ b/seed/go-sdk/enum/query_param.go @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum + +type SendEnumAsQueryParamRequest struct { + Operand *Operand `json:"-" url:"operand,omitempty"` +} + +type SendEnumListAsQueryParamRequest struct { + Operand []*Operand `json:"-" url:"operand,omitempty"` +} diff --git a/generators/go/seed/sdk/enum/queryparam/client.go b/seed/go-sdk/enum/queryparam/client.go similarity index 100% rename from generators/go/seed/sdk/enum/queryparam/client.go rename to seed/go-sdk/enum/queryparam/client.go diff --git a/seed/go-sdk/enum/snippet.json b/seed/go-sdk/enum/snippet.json new file mode 100644 index 00000000000..525f077b55b --- /dev/null +++ b/seed/go-sdk/enum/snippet.json @@ -0,0 +1,34 @@ +{ + "endpoints": [ + { + "id": { + "path": "/inlined", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/enum/fern\"\n\tfernclient \"github.com/enum/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.InlinedRequest.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendEnumInlinedRequest{\n\t\tOperand: fern.Operand,\n\t},\n)\n" + } + }, + { + "id": { + "path": "/path/{operand}", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/enum/fern\"\n\tfernclient \"github.com/enum/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.PathParam.Send(\n\tcontext.TODO(),\n\tfern.Operand,\n)\n" + } + }, + { + "id": { + "path": "/query", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/enum/fern\"\n\tfernclient \"github.com/enum/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.QueryParam.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendEnumAsQueryParamRequest{\n\t\tOperand: fern.Operand,\n\t},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/enum/types.go b/seed/go-sdk/enum/types.go new file mode 100644 index 00000000000..68bc8963a63 --- /dev/null +++ b/seed/go-sdk/enum/types.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package enum + +import ( + fmt "fmt" +) + +// Tests enum name and value can be +// different. +type Operand string + +const ( + OperandGreaterThan Operand = ">" + OperandEqualTo Operand = "=" + // The name and value should be similar + // are similar for less than. + OperandLessThan Operand = "less_than" +) + +func NewOperandFromString(s string) (Operand, error) { + switch s { + case ">": + return OperandGreaterThan, nil + case "=": + return OperandEqualTo, nil + case "less_than": + return OperandLessThan, nil + } + var t Operand + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (o Operand) Ptr() *Operand { + return &o +} diff --git a/seed/go-sdk/error-property/.github/workflows/ci.yml b/seed/go-sdk/error-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/error-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/error-property/client/client.go b/seed/go-sdk/error-property/client/client.go new file mode 100644 index 00000000000..2b2c188b2af --- /dev/null +++ b/seed/go-sdk/error-property/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/error-property/fern/core" + option "github.com/error-property/fern/option" + propertybasederror "github.com/error-property/fern/propertybasederror" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + PropertyBasedError *propertybasederror.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + PropertyBasedError: propertybasederror.NewClient(opts...), + } +} diff --git a/seed/go-sdk/error-property/client/client_test.go b/seed/go-sdk/error-property/client/client_test.go new file mode 100644 index 00000000000..f55d04408c4 --- /dev/null +++ b/seed/go-sdk/error-property/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/error-property/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/error-property/core/core.go b/seed/go-sdk/error-property/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/error-property/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/error-property/core/core_test.go b/seed/go-sdk/error-property/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/error-property/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/error-property/core/query.go b/seed/go-sdk/error-property/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/error-property/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/error-property/core/query_test.go b/seed/go-sdk/error-property/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/error-property/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/error-property/core/request_option.go b/seed/go-sdk/error-property/core/request_option.go new file mode 100644 index 00000000000..537f1f2411e --- /dev/null +++ b/seed/go-sdk/error-property/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/error-property/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/error-property/core/retrier.go b/seed/go-sdk/error-property/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/error-property/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/error-property/core/stringer.go b/seed/go-sdk/error-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/error-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/error-property/core/time.go b/seed/go-sdk/error-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/error-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/error-property/errors.go b/seed/go-sdk/error-property/errors.go new file mode 100644 index 00000000000..7c6b68bb308 --- /dev/null +++ b/seed/go-sdk/error-property/errors.go @@ -0,0 +1,31 @@ +// This file was auto-generated by Fern from our API Definition. + +package errorproperty + +import ( + json "encoding/json" + core "github.com/error-property/fern/core" +) + +type PropertyBasedErrorTest struct { + *core.APIError + Body *PropertyBasedErrorTestBody +} + +func (p *PropertyBasedErrorTest) UnmarshalJSON(data []byte) error { + var body *PropertyBasedErrorTestBody + if err := json.Unmarshal(data, &body); err != nil { + return err + } + p.StatusCode = 400 + p.Body = body + return nil +} + +func (p *PropertyBasedErrorTest) MarshalJSON() ([]byte, error) { + return json.Marshal(p.Body) +} + +func (p *PropertyBasedErrorTest) Unwrap() error { + return p.APIError +} diff --git a/seed/go-sdk/error-property/go.mod b/seed/go-sdk/error-property/go.mod new file mode 100644 index 00000000000..1724c397964 --- /dev/null +++ b/seed/go-sdk/error-property/go.mod @@ -0,0 +1,9 @@ +module github.com/error-property/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/error-property/go.sum b/seed/go-sdk/error-property/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/error-property/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/error-property/option/request_option.go b/seed/go-sdk/error-property/option/request_option.go new file mode 100644 index 00000000000..b8b40753bac --- /dev/null +++ b/seed/go-sdk/error-property/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/error-property/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/error-property/pointer.go b/seed/go-sdk/error-property/pointer.go new file mode 100644 index 00000000000..3c5c6faa7b1 --- /dev/null +++ b/seed/go-sdk/error-property/pointer.go @@ -0,0 +1,132 @@ +package errorproperty + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/error-property/propertybasederror/client.go b/seed/go-sdk/error-property/propertybasederror/client.go new file mode 100644 index 00000000000..e28a747e0ab --- /dev/null +++ b/seed/go-sdk/error-property/propertybasederror/client.go @@ -0,0 +1,97 @@ +// This file was auto-generated by Fern from our API Definition. + +package propertybasederror + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fern "github.com/error-property/fern" + core "github.com/error-property/fern/core" + option "github.com/error-property/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// GET request that always throws an error +func (c *Client) ThrowError( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "property-based-error" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + var discriminant struct { + ErrorName string `json:"errorName"` + Content json.RawMessage `json:"content"` + } + if err := decoder.Decode(&discriminant); err != nil { + return err + } + switch discriminant.ErrorName { + case "PropertyBasedErrorTest": + value := new(fern.PropertyBasedErrorTest) + value.APIError = apiError + if err := json.Unmarshal(discriminant.Content, value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/error-property/snippet.json b/seed/go-sdk/error-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/error-property/types.go b/seed/go-sdk/error-property/types.go new file mode 100644 index 00000000000..bd25e6c7e75 --- /dev/null +++ b/seed/go-sdk/error-property/types.go @@ -0,0 +1,38 @@ +// This file was auto-generated by Fern from our API Definition. + +package errorproperty + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/error-property/fern/core" +) + +type PropertyBasedErrorTestBody struct { + Message string `json:"message" url:"message"` + + _rawJSON json.RawMessage +} + +func (p *PropertyBasedErrorTestBody) UnmarshalJSON(data []byte) error { + type unmarshaler PropertyBasedErrorTestBody + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *p = PropertyBasedErrorTestBody(value) + p._rawJSON = json.RawMessage(data) + return nil +} + +func (p *PropertyBasedErrorTestBody) String() string { + if len(p._rawJSON) > 0 { + if value, err := core.StringifyJSON(p._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(p); err == nil { + return value + } + return fmt.Sprintf("%#v", p) +} diff --git a/seed/go-sdk/examples/.github/workflows/ci.yml b/seed/go-sdk/examples/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/examples/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/examples/client/client.go b/seed/go-sdk/examples/client/client.go new file mode 100644 index 00000000000..a81b56d8e49 --- /dev/null +++ b/seed/go-sdk/examples/client/client.go @@ -0,0 +1,76 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + core "github.com/examples/fern/core" + fileclient "github.com/examples/fern/file/client" + healthclient "github.com/examples/fern/health/client" + option "github.com/examples/fern/option" + service "github.com/examples/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + File *fileclient.Client + Health *healthclient.Client + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + File: fileclient.NewClient(opts...), + Health: healthclient.NewClient(opts...), + Service: service.NewClient(opts...), + } +} + +func (c *Client) Echo( + ctx context.Context, + request string, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/examples/client/client_test.go b/seed/go-sdk/examples/client/client_test.go new file mode 100644 index 00000000000..ace2a08796d --- /dev/null +++ b/seed/go-sdk/examples/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/examples/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/examples/commons/types.go b/seed/go-sdk/examples/commons/types.go new file mode 100644 index 00000000000..567972e1519 --- /dev/null +++ b/seed/go-sdk/examples/commons/types.go @@ -0,0 +1,208 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/examples/fern/core" +) + +type Data struct { + Type string + String string + Base64 []byte +} + +func NewDataFromString(value string) *Data { + return &Data{Type: "string", String: value} +} + +func NewDataFromBase64(value []byte) *Data { + return &Data{Type: "base64", Base64: value} +} + +func (d *Data) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + d.Type = unmarshaler.Type + switch unmarshaler.Type { + case "string": + var valueUnmarshaler struct { + String string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.String = valueUnmarshaler.String + case "base64": + var valueUnmarshaler struct { + Base64 []byte `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.Base64 = valueUnmarshaler.Base64 + } + return nil +} + +func (d Data) MarshalJSON() ([]byte, error) { + switch d.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + var marshaler = struct { + Type string `json:"type"` + String string `json:"value"` + }{ + Type: d.Type, + String: d.String, + } + return json.Marshal(marshaler) + case "base64": + var marshaler = struct { + Type string `json:"type"` + Base64 []byte `json:"value"` + }{ + Type: d.Type, + Base64: d.Base64, + } + return json.Marshal(marshaler) + } +} + +type DataVisitor interface { + VisitString(string) error + VisitBase64([]byte) error +} + +func (d *Data) Accept(visitor DataVisitor) error { + switch d.Type { + default: + return fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + return visitor.VisitString(d.String) + case "base64": + return visitor.VisitBase64(d.Base64) + } +} + +type EventInfo struct { + Type string + Metadata *Metadata + Tag Tag +} + +func NewEventInfoFromMetadata(value *Metadata) *EventInfo { + return &EventInfo{Type: "metadata", Metadata: value} +} + +func NewEventInfoFromTag(value Tag) *EventInfo { + return &EventInfo{Type: "tag", Tag: value} +} + +func (e *EventInfo) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "metadata": + value := new(Metadata) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Metadata = value + case "tag": + var valueUnmarshaler struct { + Tag Tag `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + e.Tag = valueUnmarshaler.Tag + } + return nil +} + +func (e EventInfo) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + var marshaler = struct { + Type string `json:"type"` + *Metadata + }{ + Type: e.Type, + Metadata: e.Metadata, + } + return json.Marshal(marshaler) + case "tag": + var marshaler = struct { + Type string `json:"type"` + Tag Tag `json:"value"` + }{ + Type: e.Type, + Tag: e.Tag, + } + return json.Marshal(marshaler) + } +} + +type EventInfoVisitor interface { + VisitMetadata(*Metadata) error + VisitTag(Tag) error +} + +func (e *EventInfo) Accept(visitor EventInfoVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + return visitor.VisitMetadata(e.Metadata) + case "tag": + return visitor.VisitTag(e.Tag) + } +} + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` + JsonString *string `json:"jsonString,omitempty" url:"jsonString,omitempty"` + + _rawJSON json.RawMessage +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + type unmarshaler Metadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Metadata(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Metadata) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Tag = string diff --git a/seed/go-sdk/examples/core/core.go b/seed/go-sdk/examples/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/examples/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/examples/core/core_test.go b/seed/go-sdk/examples/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/examples/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/examples/core/query.go b/seed/go-sdk/examples/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/examples/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/examples/core/query_test.go b/seed/go-sdk/examples/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/examples/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/examples/core/request_option.go b/seed/go-sdk/examples/core/request_option.go new file mode 100644 index 00000000000..74b1436bc82 --- /dev/null +++ b/seed/go-sdk/examples/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/examples/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/examples/core/retrier.go b/seed/go-sdk/examples/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/examples/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/examples/core/stringer.go b/seed/go-sdk/examples/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/examples/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/examples/core/time.go b/seed/go-sdk/examples/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/examples/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/examples/environments.go b/seed/go-sdk/examples/environments.go new file mode 100644 index 00000000000..e46c475de38 --- /dev/null +++ b/seed/go-sdk/examples/environments.go @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Production string + Staging string +}{ + Production: "https://production.com/api", + Staging: "https://staging.com/api", +} diff --git a/seed/go-sdk/examples/errors.go b/seed/go-sdk/examples/errors.go new file mode 100644 index 00000000000..e9063f8f7f3 --- /dev/null +++ b/seed/go-sdk/examples/errors.go @@ -0,0 +1,31 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + core "github.com/examples/fern/core" +) + +type NotFoundError struct { + *core.APIError + Body string +} + +func (n *NotFoundError) UnmarshalJSON(data []byte) error { + var body string + if err := json.Unmarshal(data, &body); err != nil { + return err + } + n.StatusCode = 404 + n.Body = body + return nil +} + +func (n *NotFoundError) MarshalJSON() ([]byte, error) { + return json.Marshal(n.Body) +} + +func (n *NotFoundError) Unwrap() error { + return n.APIError +} diff --git a/seed/go-sdk/examples/file/client/client.go b/seed/go-sdk/examples/file/client/client.go new file mode 100644 index 00000000000..57acb00ff8b --- /dev/null +++ b/seed/go-sdk/examples/file/client/client.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + notificationclient "github.com/examples/fern/file/notification/client" + service "github.com/examples/fern/file/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Notification *notificationclient.Client + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Notification: notificationclient.NewClient(opts...), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/file/notification/client/client.go b/seed/go-sdk/examples/file/notification/client/client.go new file mode 100644 index 00000000000..045636bb64d --- /dev/null +++ b/seed/go-sdk/examples/file/notification/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + service "github.com/examples/fern/file/notification/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/file/notification/service/client.go b/seed/go-sdk/examples/file/notification/service/client.go new file mode 100644 index 00000000000..d6c795bc7c0 --- /dev/null +++ b/seed/go-sdk/examples/file/notification/service/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetException( + ctx context.Context, + notificationId string, + opts ...option.RequestOption, +) (*fern.Exception, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"file/notification/%v", notificationId) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.Exception + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/file/service.go b/seed/go-sdk/examples/file/service.go new file mode 100644 index 00000000000..5253d0234d9 --- /dev/null +++ b/seed/go-sdk/examples/file/service.go @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type GetFileRequest struct { +} diff --git a/seed/go-sdk/examples/file/service/client.go b/seed/go-sdk/examples/file/service/client.go new file mode 100644 index 00000000000..cdddbe721b3 --- /dev/null +++ b/seed/go-sdk/examples/file/service/client.go @@ -0,0 +1,95 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fmt "fmt" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + file "github.com/examples/fern/file" + option "github.com/examples/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// This endpoint returns a file by its name. +func (c *Client) GetFile( + ctx context.Context, + // This is a filename + filename string, + request *file.GetFileRequest, + opts ...option.RequestOption, +) (*fern.File, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"file/%v", filename) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 404: + value := new(fern.NotFoundError) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response *fern.File + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/file/types.go b/seed/go-sdk/examples/file/types.go new file mode 100644 index 00000000000..edc509f893e --- /dev/null +++ b/seed/go-sdk/examples/file/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type Filename = string diff --git a/seed/go-sdk/examples/go.mod b/seed/go-sdk/examples/go.mod new file mode 100644 index 00000000000..f75913fe054 --- /dev/null +++ b/seed/go-sdk/examples/go.mod @@ -0,0 +1,9 @@ +module github.com/examples/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/examples/go.sum b/seed/go-sdk/examples/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/examples/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/examples/health/client/client.go b/seed/go-sdk/examples/health/client/client.go new file mode 100644 index 00000000000..1fb05e0b7d5 --- /dev/null +++ b/seed/go-sdk/examples/health/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + service "github.com/examples/fern/health/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/health/service/client.go b/seed/go-sdk/examples/health/service/client.go new file mode 100644 index 00000000000..1627e1dc1ec --- /dev/null +++ b/seed/go-sdk/examples/health/service/client.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// This endpoint checks the health of a resource. +func (c *Client) Check( + ctx context.Context, + // The id to check + id string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"check/%v", id) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} + +// This endpoint checks the health of the service. +func (c *Client) Ping( + ctx context.Context, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "ping" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return false, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/option/request_option.go b/seed/go-sdk/examples/option/request_option.go new file mode 100644 index 00000000000..8c95bd8179c --- /dev/null +++ b/seed/go-sdk/examples/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/examples/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/examples/pointer.go b/seed/go-sdk/examples/pointer.go new file mode 100644 index 00000000000..6938b531c04 --- /dev/null +++ b/seed/go-sdk/examples/pointer.go @@ -0,0 +1,132 @@ +package examples + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/examples/service.go b/seed/go-sdk/examples/service.go new file mode 100644 index 00000000000..c04c639bfd8 --- /dev/null +++ b/seed/go-sdk/examples/service.go @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +type GetMetadataRequest struct { + XApiVersion string `json:"-" url:"-"` + Shallow *bool `json:"-" url:"shallow,omitempty"` + Tag []*string `json:"-" url:"tag,omitempty"` +} diff --git a/generators/go/seed/sdk/literal/literal/client.go b/seed/go-sdk/examples/service/client.go similarity index 71% rename from generators/go/seed/sdk/literal/literal/client.go rename to seed/go-sdk/examples/service/client.go index ba563078811..940c1d60b4a 100644 --- a/generators/go/seed/sdk/literal/literal/client.go +++ b/seed/go-sdk/examples/service/client.go @@ -1,12 +1,13 @@ // This file was auto-generated by Fern from our API Definition. -package literal +package service import ( context "context" - fern "github.com/literal/fern" - core "github.com/literal/fern/core" - option "github.com/literal/fern/option" + fmt "fmt" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" http "net/http" ) @@ -30,11 +31,11 @@ func NewClient(opts ...option.RequestOption) *Client { } } -func (c *Client) CreateOptions( +func (c *Client) GetMovie( ctx context.Context, - request *fern.CreateOptionsRequest, + movieId fern.MovieId, opts ...option.RequestOption, -) (*fern.CreateOptionsResponse, error) { +) (*fern.Movie, error) { options := core.NewRequestOptions(opts...) baseURL := "" @@ -44,20 +45,19 @@ func (c *Client) CreateOptions( if options.BaseURL != "" { baseURL = options.BaseURL } - endpointURL := baseURL + "/" + "options" + endpointURL := fmt.Sprintf(baseURL+"/"+"movie/%v", movieId) headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) - var response *fern.CreateOptionsResponse + var response *fern.Movie if err := c.caller.Call( ctx, &core.CallParams{ URL: endpointURL, - Method: http.MethodPost, + Method: http.MethodGet, MaxAttempts: options.MaxAttempts, Headers: headers, Client: options.HTTPClient, - Request: request, Response: &response, }, ); err != nil { @@ -66,11 +66,11 @@ func (c *Client) CreateOptions( return response, nil } -func (c *Client) GetOptions( +func (c *Client) CreateMovie( ctx context.Context, - request *fern.GetOptionsRequest, + request *fern.Movie, opts ...option.RequestOption, -) (*fern.Options, error) { +) (fern.MovieId, error) { options := core.NewRequestOptions(opts...) baseURL := "" @@ -80,11 +80,11 @@ func (c *Client) GetOptions( if options.BaseURL != "" { baseURL = options.BaseURL } - endpointURL := baseURL + "/" + "options" + endpointURL := baseURL + "/" + "movie" headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) - var response *fern.Options + var response fern.MovieId if err := c.caller.Call( ctx, &core.CallParams{ @@ -97,16 +97,16 @@ func (c *Client) GetOptions( Response: &response, }, ); err != nil { - return nil, err + return "", err } return response, nil } -func (c *Client) GetUndiscriminatedOptions( +func (c *Client) GetMetadata( ctx context.Context, - request *fern.GetUndiscriminatedOptionsRequest, + request *fern.GetMetadataRequest, opts ...option.RequestOption, -) (*fern.UndiscriminatedOptions, error) { +) (*fern.Metadata, error) { options := core.NewRequestOptions(opts...) baseURL := "" @@ -116,20 +116,28 @@ func (c *Client) GetUndiscriminatedOptions( if options.BaseURL != "" { baseURL = options.BaseURL } - endpointURL := baseURL + "/" + "options" + endpointURL := baseURL + "/" + "metadata" + + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers.Add("X-API-Version", fmt.Sprintf("%v", request.XApiVersion)) - var response *fern.UndiscriminatedOptions + var response *fern.Metadata if err := c.caller.Call( ctx, &core.CallParams{ URL: endpointURL, - Method: http.MethodPost, + Method: http.MethodGet, MaxAttempts: options.MaxAttempts, Headers: headers, Client: options.HTTPClient, - Request: request, Response: &response, }, ); err != nil { diff --git a/seed/go-sdk/examples/snippet.json b/seed/go-sdk/examples/snippet.json new file mode 100644 index 00000000000..90488c9a876 --- /dev/null +++ b/seed/go-sdk/examples/snippet.json @@ -0,0 +1,84 @@ +{ + "endpoints": [ + { + "id": { + "path": "/", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Echo(\n\tcontext.TODO(),\n\t\"Hello world!\",\n)\n" + } + }, + { + "id": { + "path": "/check/{id}", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nerr := client.Health.Service.Check(\n\tcontext.TODO(),\n\t\"id-2sdx82h\",\n)\n" + } + }, + { + "id": { + "path": "/file/notification/{notificationId}", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.File.Notification.Service.GetException(\n\tcontext.TODO(),\n\t\"notification-hsy129x\",\n)\n" + } + }, + { + "id": { + "path": "/file/{filename}", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\tfile \"github.com/examples/fern/file\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.File.Service.GetFile(\n\tcontext.TODO(),\n\t\"file.txt\",\n\t\u0026file.GetFileRequest{\n\t\tXFileApiVersion: \"0.0.2\",\n\t},\n)\n" + } + }, + { + "id": { + "path": "/metadata", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.GetMetadata(\n\tcontext.TODO(),\n\t\u0026fern.GetMetadataRequest{\n\t\tXApiVersion: \"0.0.1\",\n\t\tShallow: fern.Bool(\n\t\t\tfalse,\n\t\t),\n\t\tTag: []*string{\n\t\t\tfern.String(\n\t\t\t\t\"development\",\n\t\t\t),\n\t\t},\n\t},\n)\n" + } + }, + { + "id": { + "path": "/movie", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.CreateMovie(\n\tcontext.TODO(),\n\t\u0026fern.Movie{\n\t\tId: \"movie-c06a4ad7\",\n\t\tTitle: \"The Boy and the Heron\",\n\t\tFrom: \"Hayao Miyazaki\",\n\t\tRating: 8,\n\t\tTag: \"tag-wf9as23d\",\n\t},\n)\n" + } + }, + { + "id": { + "path": "/movie/{movieId}", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.GetMovie(\n\tcontext.TODO(),\n\t\"movie-c06a4ad7\",\n)\n" + } + }, + { + "id": { + "path": "/ping", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Health.Service.Ping(\n\tcontext.TODO(),\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/examples/types.go b/seed/go-sdk/examples/types.go new file mode 100644 index 00000000000..6d10c253b2e --- /dev/null +++ b/seed/go-sdk/examples/types.go @@ -0,0 +1,874 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + fmt "fmt" + commons "github.com/examples/fern/commons" + core "github.com/examples/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type Actor struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` + + _rawJSON json.RawMessage +} + +func (a *Actor) UnmarshalJSON(data []byte) error { + type unmarshaler Actor + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = Actor(value) + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *Actor) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type Actress struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` + + _rawJSON json.RawMessage +} + +func (a *Actress) UnmarshalJSON(data []byte) error { + type unmarshaler Actress + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = Actress(value) + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *Actress) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type CastMember struct { + typeName string + Actor *Actor + Actress *Actress + StuntDouble *StuntDouble +} + +func NewCastMemberFromActor(value *Actor) *CastMember { + return &CastMember{typeName: "actor", Actor: value} +} + +func NewCastMemberFromActress(value *Actress) *CastMember { + return &CastMember{typeName: "actress", Actress: value} +} + +func NewCastMemberFromStuntDouble(value *StuntDouble) *CastMember { + return &CastMember{typeName: "stuntDouble", StuntDouble: value} +} + +func (c *CastMember) UnmarshalJSON(data []byte) error { + valueActor := new(Actor) + if err := json.Unmarshal(data, &valueActor); err == nil { + c.typeName = "actor" + c.Actor = valueActor + return nil + } + valueActress := new(Actress) + if err := json.Unmarshal(data, &valueActress); err == nil { + c.typeName = "actress" + c.Actress = valueActress + return nil + } + valueStuntDouble := new(StuntDouble) + if err := json.Unmarshal(data, &valueStuntDouble); err == nil { + c.typeName = "stuntDouble" + c.StuntDouble = valueStuntDouble + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, c) +} + +func (c CastMember) MarshalJSON() ([]byte, error) { + switch c.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return json.Marshal(c.Actor) + case "actress": + return json.Marshal(c.Actress) + case "stuntDouble": + return json.Marshal(c.StuntDouble) + } +} + +type CastMemberVisitor interface { + VisitActor(*Actor) error + VisitActress(*Actress) error + VisitStuntDouble(*StuntDouble) error +} + +func (c *CastMember) Accept(visitor CastMemberVisitor) error { + switch c.typeName { + default: + return fmt.Errorf("invalid type %s in %T", c.typeName, c) + case "actor": + return visitor.VisitActor(c.Actor) + case "actress": + return visitor.VisitActress(c.Actress) + case "stuntDouble": + return visitor.VisitStuntDouble(c.StuntDouble) + } +} + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` + + _rawJSON json.RawMessage +} + +func (d *Directory) UnmarshalJSON(data []byte) error { + type unmarshaler Directory + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Directory(value) + d._rawJSON = json.RawMessage(data) + return nil +} + +func (d *Directory) String() string { + if len(d._rawJSON) > 0 { + if value, err := core.StringifyJSON(d._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type Exception struct { + Type string + Generic *ExceptionInfo + Timeout interface{} +} + +func NewExceptionFromGeneric(value *ExceptionInfo) *Exception { + return &Exception{Type: "generic", Generic: value} +} + +func NewExceptionFromTimeout(value interface{}) *Exception { + return &Exception{Type: "timeout", Timeout: value} +} + +func (e *Exception) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "generic": + value := new(ExceptionInfo) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Generic = value + case "timeout": + value := make(map[string]interface{}) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Timeout = value + } + return nil +} + +func (e Exception) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + var marshaler = struct { + Type string `json:"type"` + *ExceptionInfo + }{ + Type: e.Type, + ExceptionInfo: e.Generic, + } + return json.Marshal(marshaler) + case "timeout": + var marshaler = struct { + Type string `json:"type"` + Timeout interface{} `json:"timeout,omitempty"` + }{ + Type: e.Type, + Timeout: e.Timeout, + } + return json.Marshal(marshaler) + } +} + +type ExceptionVisitor interface { + VisitGeneric(*ExceptionInfo) error + VisitTimeout(interface{}) error +} + +func (e *Exception) Accept(visitor ExceptionVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + return visitor.VisitGeneric(e.Generic) + case "timeout": + return visitor.VisitTimeout(e.Timeout) + } +} + +type ExceptionInfo struct { + ExceptionType string `json:"exceptionType" url:"exceptionType"` + ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` + ExceptionStacktrace string `json:"exceptionStacktrace" url:"exceptionStacktrace"` + + _rawJSON json.RawMessage +} + +func (e *ExceptionInfo) UnmarshalJSON(data []byte) error { + type unmarshaler ExceptionInfo + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *e = ExceptionInfo(value) + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *ExceptionInfo) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type ExtendedMovie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + Cast []string `json:"cast,omitempty" url:"cast,omitempty"` + type_ string + + _rawJSON json.RawMessage +} + +func (e *ExtendedMovie) Type() string { + return e.type_ +} + +func (e *ExtendedMovie) UnmarshalJSON(data []byte) error { + type embed ExtendedMovie + var unmarshaler = struct { + embed + }{ + embed: embed(*e), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *e = ExtendedMovie(unmarshaler.embed) + e.type_ = "movie" + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *ExtendedMovie) MarshalJSON() ([]byte, error) { + type embed ExtendedMovie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*e), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (e *ExtendedMovie) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` + + _rawJSON json.RawMessage +} + +func (f *File) UnmarshalJSON(data []byte) error { + type unmarshaler File + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = File(value) + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *File) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type Metadata struct { + Type string + Extra map[string]string + Tags []string + Html string + Markdown string +} + +func NewMetadataFromHtml(value string) *Metadata { + return &Metadata{Type: "html", Html: value} +} + +func NewMetadataFromMarkdown(value string) *Metadata { + return &Metadata{Type: "markdown", Markdown: value} +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + m.Type = unmarshaler.Type + m.Extra = unmarshaler.Extra + m.Tags = unmarshaler.Tags + switch unmarshaler.Type { + case "html": + var valueUnmarshaler struct { + Html string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Html = valueUnmarshaler.Html + case "markdown": + var valueUnmarshaler struct { + Markdown string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Markdown = valueUnmarshaler.Markdown + } + return nil +} + +func (m Metadata) MarshalJSON() ([]byte, error) { + switch m.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Html string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Html: m.Html, + } + return json.Marshal(marshaler) + case "markdown": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra,omitempty"` + Tags []string `json:"tags,omitempty"` + Markdown string `json:"value"` + }{ + Type: m.Type, + Extra: m.Extra, + Tags: m.Tags, + Markdown: m.Markdown, + } + return json.Marshal(marshaler) + } +} + +type MetadataVisitor interface { + VisitHtml(string) error + VisitMarkdown(string) error +} + +func (m *Metadata) Accept(visitor MetadataVisitor) error { + switch m.Type { + default: + return fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + return visitor.VisitHtml(m.Html) + case "markdown": + return visitor.VisitMarkdown(m.Markdown) + } +} + +type Migration struct { + Name string `json:"name" url:"name"` + Status MigrationStatus `json:"status,omitempty" url:"status,omitempty"` + + _rawJSON json.RawMessage +} + +func (m *Migration) UnmarshalJSON(data []byte) error { + type unmarshaler Migration + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Migration(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Migration) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MigrationStatus string + +const ( + // The migration is running. + MigrationStatusRunning MigrationStatus = "RUNNING" + // The migration failed. + MigrationStatusFailed MigrationStatus = "FAILED" + MigrationStatusFinished MigrationStatus = "FINISHED" +) + +func NewMigrationStatusFromString(s string) (MigrationStatus, error) { + switch s { + case "RUNNING": + return MigrationStatusRunning, nil + case "FAILED": + return MigrationStatusFailed, nil + case "FINISHED": + return MigrationStatusFinished, nil + } + var t MigrationStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (m MigrationStatus) Ptr() *MigrationStatus { + return &m +} + +type Moment struct { + Id uuid.UUID `json:"id" url:"id"` + Date time.Time `json:"date" url:"date" format:"date"` + Datetime time.Time `json:"datetime" url:"datetime"` + + _rawJSON json.RawMessage +} + +func (m *Moment) UnmarshalJSON(data []byte) error { + type embed Moment + var unmarshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Moment(unmarshaler.embed) + m.Date = unmarshaler.Date.Time() + m.Datetime = unmarshaler.Datetime.Time() + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Moment) MarshalJSON() ([]byte, error) { + type embed Moment + var marshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + Date: core.NewDate(m.Date), + Datetime: core.NewDateTime(m.Datetime), + } + return json.Marshal(marshaler) +} + +func (m *Moment) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + type_ string + + _rawJSON json.RawMessage +} + +func (m *Movie) Type() string { + return m.type_ +} + +func (m *Movie) UnmarshalJSON(data []byte) error { + type embed Movie + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Movie(unmarshaler.embed) + m.type_ = "movie" + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Movie) MarshalJSON() ([]byte, error) { + type embed Movie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*m), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (m *Movie) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string + +type Node struct { + Name string `json:"name" url:"name"` + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + Trees []*Tree `json:"trees,omitempty" url:"trees,omitempty"` + + _rawJSON json.RawMessage +} + +func (n *Node) UnmarshalJSON(data []byte) error { + type unmarshaler Node + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *n = Node(value) + n._rawJSON = json.RawMessage(data) + return nil +} + +func (n *Node) String() string { + if len(n._rawJSON) > 0 { + if value, err := core.StringifyJSON(n._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Request struct { + Request interface{} `json:"request,omitempty" url:"request,omitempty"` + + _rawJSON json.RawMessage +} + +func (r *Request) UnmarshalJSON(data []byte) error { + type unmarshaler Request + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = Request(value) + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *Request) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Response struct { + Response interface{} `json:"response,omitempty" url:"response,omitempty"` + + _rawJSON json.RawMessage +} + +func (r *Response) UnmarshalJSON(data []byte) error { + type unmarshaler Response + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = Response(value) + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *Response) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type StuntDouble struct { + Name string `json:"name" url:"name"` + ActorOrActressId string `json:"actorOrActressId" url:"actorOrActressId"` + + _rawJSON json.RawMessage +} + +func (s *StuntDouble) UnmarshalJSON(data []byte) error { + type unmarshaler StuntDouble + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *s = StuntDouble(value) + s._rawJSON = json.RawMessage(data) + return nil +} + +func (s *StuntDouble) String() string { + if len(s._rawJSON) > 0 { + if value, err := core.StringifyJSON(s._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type Test struct { + Type string + And bool + Or bool +} + +func NewTestFromAnd(value bool) *Test { + return &Test{Type: "and", And: value} +} + +func NewTestFromOr(value bool) *Test { + return &Test{Type: "or", Or: value} +} + +func (t *Test) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + t.Type = unmarshaler.Type + switch unmarshaler.Type { + case "and": + var valueUnmarshaler struct { + And bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.And = valueUnmarshaler.And + case "or": + var valueUnmarshaler struct { + Or bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.Or = valueUnmarshaler.Or + } + return nil +} + +func (t Test) MarshalJSON() ([]byte, error) { + switch t.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + var marshaler = struct { + Type string `json:"type"` + And bool `json:"value"` + }{ + Type: t.Type, + And: t.And, + } + return json.Marshal(marshaler) + case "or": + var marshaler = struct { + Type string `json:"type"` + Or bool `json:"value"` + }{ + Type: t.Type, + Or: t.Or, + } + return json.Marshal(marshaler) + } +} + +type TestVisitor interface { + VisitAnd(bool) error + VisitOr(bool) error +} + +func (t *Test) Accept(visitor TestVisitor) error { + switch t.Type { + default: + return fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + return visitor.VisitAnd(t.And) + case "or": + return visitor.VisitOr(t.Or) + } +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + + _rawJSON json.RawMessage +} + +func (t *Tree) UnmarshalJSON(data []byte) error { + type unmarshaler Tree + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *t = Tree(value) + t._rawJSON = json.RawMessage(data) + return nil +} + +func (t *Tree) String() string { + if len(t._rawJSON) > 0 { + if value, err := core.StringifyJSON(t._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-sdk/extends/.github/workflows/ci.yml b/seed/go-sdk/extends/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/extends/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/extends/client/client.go b/seed/go-sdk/extends/client/client.go new file mode 100644 index 00000000000..fcb1190b97a --- /dev/null +++ b/seed/go-sdk/extends/client/client.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/extends/fern/core" + option "github.com/extends/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} diff --git a/seed/go-sdk/extends/client/client_test.go b/seed/go-sdk/extends/client/client_test.go new file mode 100644 index 00000000000..1cf461283ff --- /dev/null +++ b/seed/go-sdk/extends/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/extends/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/extends/core/core.go b/seed/go-sdk/extends/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/extends/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/extends/core/core_test.go b/seed/go-sdk/extends/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/extends/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/extends/core/query.go b/seed/go-sdk/extends/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/extends/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/extends/core/query_test.go b/seed/go-sdk/extends/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/extends/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/extends/core/request_option.go b/seed/go-sdk/extends/core/request_option.go new file mode 100644 index 00000000000..c18ad60b575 --- /dev/null +++ b/seed/go-sdk/extends/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/extends/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/extends/core/retrier.go b/seed/go-sdk/extends/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/extends/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/extends/core/stringer.go b/seed/go-sdk/extends/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/extends/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/extends/core/time.go b/seed/go-sdk/extends/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/extends/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/extends/go.mod b/seed/go-sdk/extends/go.mod new file mode 100644 index 00000000000..c71ac357193 --- /dev/null +++ b/seed/go-sdk/extends/go.mod @@ -0,0 +1,9 @@ +module github.com/extends/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/extends/go.sum b/seed/go-sdk/extends/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/extends/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/extends/option/request_option.go b/seed/go-sdk/extends/option/request_option.go new file mode 100644 index 00000000000..b986ab8b3c5 --- /dev/null +++ b/seed/go-sdk/extends/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/extends/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/extends/pointer.go b/seed/go-sdk/extends/pointer.go new file mode 100644 index 00000000000..47f4afdd153 --- /dev/null +++ b/seed/go-sdk/extends/pointer.go @@ -0,0 +1,132 @@ +package extends + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/extends/snippet.json b/seed/go-sdk/extends/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/extends/types.go b/seed/go-sdk/extends/types.go new file mode 100644 index 00000000000..9a8c104eebb --- /dev/null +++ b/seed/go-sdk/extends/types.go @@ -0,0 +1,129 @@ +// This file was auto-generated by Fern from our API Definition. + +package extends + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/extends/fern/core" +) + +type Docs struct { + Docs string `json:"docs" url:"docs"` + + _rawJSON json.RawMessage +} + +func (d *Docs) UnmarshalJSON(data []byte) error { + type unmarshaler Docs + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Docs(value) + d._rawJSON = json.RawMessage(data) + return nil +} + +func (d *Docs) String() string { + if len(d._rawJSON) > 0 { + if value, err := core.StringifyJSON(d._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type ExampleType struct { + Docs string `json:"docs" url:"docs"` + Name string `json:"name" url:"name"` + + _rawJSON json.RawMessage +} + +func (e *ExampleType) UnmarshalJSON(data []byte) error { + type unmarshaler ExampleType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *e = ExampleType(value) + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *ExampleType) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type Json struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` + + _rawJSON json.RawMessage +} + +func (j *Json) UnmarshalJSON(data []byte) error { + type unmarshaler Json + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *j = Json(value) + j._rawJSON = json.RawMessage(data) + return nil +} + +func (j *Json) String() string { + if len(j._rawJSON) > 0 { + if value, err := core.StringifyJSON(j._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(j); err == nil { + return value + } + return fmt.Sprintf("%#v", j) +} + +type NestedType struct { + Docs string `json:"docs" url:"docs"` + Raw string `json:"raw" url:"raw"` + Name string `json:"name" url:"name"` + + _rawJSON json.RawMessage +} + +func (n *NestedType) UnmarshalJSON(data []byte) error { + type unmarshaler NestedType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *n = NestedType(value) + n._rawJSON = json.RawMessage(data) + return nil +} + +func (n *NestedType) String() string { + if len(n._rawJSON) > 0 { + if value, err := core.StringifyJSON(n._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} diff --git a/seed/go-sdk/file-download/.github/workflows/ci.yml b/seed/go-sdk/file-download/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/file-download/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/file-download/client/client.go b/seed/go-sdk/file-download/client/client.go new file mode 100644 index 00000000000..077e8c8d612 --- /dev/null +++ b/seed/go-sdk/file-download/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/file-download/fern/core" + option "github.com/file-download/fern/option" + service "github.com/file-download/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/file-download/client/client_test.go b/seed/go-sdk/file-download/client/client_test.go new file mode 100644 index 00000000000..f26ec256918 --- /dev/null +++ b/seed/go-sdk/file-download/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/file-download/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/file-download/core/core.go b/seed/go-sdk/file-download/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/file-download/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/file-download/core/core_test.go b/seed/go-sdk/file-download/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/file-download/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/file-download/core/query.go b/seed/go-sdk/file-download/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/file-download/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/file-download/core/query_test.go b/seed/go-sdk/file-download/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/file-download/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/file-download/core/request_option.go b/seed/go-sdk/file-download/core/request_option.go new file mode 100644 index 00000000000..86ab2dcf816 --- /dev/null +++ b/seed/go-sdk/file-download/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/file-download/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/file-download/core/retrier.go b/seed/go-sdk/file-download/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/file-download/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/file-download/core/stringer.go b/seed/go-sdk/file-download/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/file-download/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/file-download/core/time.go b/seed/go-sdk/file-download/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/file-download/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/file-download/go.mod b/seed/go-sdk/file-download/go.mod new file mode 100644 index 00000000000..5adaadafd79 --- /dev/null +++ b/seed/go-sdk/file-download/go.mod @@ -0,0 +1,9 @@ +module github.com/file-download/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/file-download/go.sum b/seed/go-sdk/file-download/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/file-download/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/file-download/option/request_option.go b/seed/go-sdk/file-download/option/request_option.go new file mode 100644 index 00000000000..d1965e4dc20 --- /dev/null +++ b/seed/go-sdk/file-download/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/file-download/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/file-download/pointer.go b/seed/go-sdk/file-download/pointer.go new file mode 100644 index 00000000000..1563946f43f --- /dev/null +++ b/seed/go-sdk/file-download/pointer.go @@ -0,0 +1,132 @@ +package filedownload + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/file-download/service/client.go b/seed/go-sdk/file-download/service/client.go new file mode 100644 index 00000000000..afd6c0ad258 --- /dev/null +++ b/seed/go-sdk/file-download/service/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + bytes "bytes" + context "context" + core "github.com/file-download/fern/core" + option "github.com/file-download/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) DownloadFile( + ctx context.Context, + opts ...option.RequestOption, +) (io.Reader, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + response := bytes.NewBuffer(nil) + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/file-download/snippet.json b/seed/go-sdk/file-download/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/file-upload/.github/workflows/ci.yml b/seed/go-sdk/file-upload/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/file-upload/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/file-upload/client/client.go b/seed/go-sdk/file-upload/client/client.go similarity index 100% rename from generators/go/seed/sdk/file-upload/client/client.go rename to seed/go-sdk/file-upload/client/client.go diff --git a/generators/go/seed/sdk/file-upload/client/client_test.go b/seed/go-sdk/file-upload/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/file-upload/client/client_test.go rename to seed/go-sdk/file-upload/client/client_test.go diff --git a/seed/go-sdk/file-upload/core/core.go b/seed/go-sdk/file-upload/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/file-upload/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/file-upload/core/core_test.go b/seed/go-sdk/file-upload/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/file-upload/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/file-upload/core/query.go b/seed/go-sdk/file-upload/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/file-upload/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/file-upload/core/query_test.go b/seed/go-sdk/file-upload/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/file-upload/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/file-upload/core/request_option.go b/seed/go-sdk/file-upload/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/file-upload/core/request_option.go rename to seed/go-sdk/file-upload/core/request_option.go diff --git a/seed/go-sdk/file-upload/core/retrier.go b/seed/go-sdk/file-upload/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/file-upload/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/file-upload/core/stringer.go b/seed/go-sdk/file-upload/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/file-upload/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/file-upload/core/time.go b/seed/go-sdk/file-upload/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/file-upload/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/file-upload/go.mod b/seed/go-sdk/file-upload/go.mod similarity index 100% rename from generators/go/seed/sdk/file-upload/go.mod rename to seed/go-sdk/file-upload/go.mod diff --git a/seed/go-sdk/file-upload/go.sum b/seed/go-sdk/file-upload/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/file-upload/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/file-upload/option/request_option.go b/seed/go-sdk/file-upload/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/file-upload/option/request_option.go rename to seed/go-sdk/file-upload/option/request_option.go diff --git a/generators/go/seed/sdk/file-upload/pointer.go b/seed/go-sdk/file-upload/pointer.go similarity index 75% rename from generators/go/seed/sdk/file-upload/pointer.go rename to seed/go-sdk/file-upload/pointer.go index d987e25bc05..cf7df204e97 100644 --- a/generators/go/seed/sdk/file-upload/pointer.go +++ b/seed/go-sdk/file-upload/pointer.go @@ -1,6 +1,10 @@ package fileupload -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/file-upload/service.go b/seed/go-sdk/file-upload/service.go new file mode 100644 index 00000000000..ff0ac3a8f6a --- /dev/null +++ b/seed/go-sdk/file-upload/service.go @@ -0,0 +1,54 @@ +// This file was auto-generated by Fern from our API Definition. + +package fileupload + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/file-upload/fern/core" +) + +type JustFileWithQueryParamsRequet struct { + MaybeString *string `json:"-" url:"maybeString,omitempty"` + Integer int `json:"-" url:"integer"` + MaybeInteger *int `json:"-" url:"maybeInteger,omitempty"` + ListOfStrings []string `json:"-" url:"listOfStrings"` + OptionalListOfStrings []*string `json:"-" url:"optionalListOfStrings,omitempty"` +} + +type MyRequest struct { + MaybeString *string `json:"maybeString,omitempty" url:"maybeString,omitempty"` + Integer int `json:"integer" url:"integer"` + MaybeInteger *int `json:"maybeInteger,omitempty" url:"maybeInteger,omitempty"` + OptionalListOfStrings []string `json:"optionalListOfStrings,omitempty" url:"optionalListOfStrings,omitempty"` + ListOfObjects []*MyObject `json:"listOfObjects,omitempty" url:"listOfObjects,omitempty"` +} + +type MyObject struct { + Foo string `json:"foo" url:"foo"` + + _rawJSON json.RawMessage +} + +func (m *MyObject) UnmarshalJSON(data []byte) error { + type unmarshaler MyObject + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = MyObject(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *MyObject) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/generators/go/seed/sdk/file-upload/service/client.go b/seed/go-sdk/file-upload/service/client.go similarity index 86% rename from generators/go/seed/sdk/file-upload/service/client.go rename to seed/go-sdk/file-upload/service/client.go index 63cd75764d8..8f56c00b007 100644 --- a/generators/go/seed/sdk/file-upload/service/client.go +++ b/seed/go-sdk/file-upload/service/client.go @@ -37,7 +37,9 @@ func NewClient(opts ...option.RequestOption) *Client { func (c *Client) Post( ctx context.Context, file io.Reader, + fileList io.Reader, maybeFile io.Reader, + maybeFileList io.Reader, request *fern.MyRequest, opts ...option.RequestOption, ) error { @@ -67,6 +69,17 @@ func (c *Client) Post( if _, err := io.Copy(filePart, file); err != nil { return err } + fileListFilename := "fileList_filename" + if named, ok := fileList.(interface{ Name() string }); ok { + fileListFilename = named.Name() + } + fileListPart, err := writer.CreateFormFile("fileList", fileListFilename) + if err != nil { + return err + } + if _, err := io.Copy(fileListPart, fileList); err != nil { + return err + } if maybeFile != nil { maybeFileFilename := "maybeFile_filename" if named, ok := maybeFile.(interface{ Name() string }); ok { @@ -80,6 +93,19 @@ func (c *Client) Post( return err } } + if maybeFileList != nil { + maybeFileListFilename := "maybeFileList_filename" + if named, ok := maybeFileList.(interface{ Name() string }); ok { + maybeFileListFilename = named.Name() + } + maybeFileListPart, err := writer.CreateFormFile("maybeFileList", maybeFileListFilename) + if err != nil { + return err + } + if _, err := io.Copy(maybeFileListPart, maybeFileList); err != nil { + return err + } + } if request.MaybeString != nil { if err := writer.WriteField("maybeString", fmt.Sprintf("%v", *request.MaybeString)); err != nil { return err @@ -93,38 +119,11 @@ func (c *Client) Post( return err } } - if err := core.WriteMultipartJSON(writer, "listOfStrings", request.ListOfStrings); err != nil { - return err - } - if err := core.WriteMultipartJSON(writer, "setOfStrings", request.SetOfStrings); err != nil { - return err - } if request.OptionalListOfStrings != nil { if err := core.WriteMultipartJSON(writer, "optionalListOfStrings", request.OptionalListOfStrings); err != nil { return err } } - if request.OptionalSetOfStrings != nil { - if err := core.WriteMultipartJSON(writer, "optionalSetOfStrings", request.OptionalSetOfStrings); err != nil { - return err - } - } - if err := core.WriteMultipartJSON(writer, "maybeList", request.MaybeList); err != nil { - return err - } - if request.OptionalMaybeList != nil { - if err := core.WriteMultipartJSON(writer, "optionalMaybeList", *request.OptionalMaybeList); err != nil { - return err - } - } - if err := core.WriteMultipartJSON(writer, "maybeListOrSet", request.MaybeListOrSet); err != nil { - return err - } - if request.OptionalMaybeListOrSet != nil { - if err := core.WriteMultipartJSON(writer, "optionalMaybeListOrSet", *request.OptionalMaybeListOrSet); err != nil { - return err - } - } if err := core.WriteMultipartJSON(writer, "listOfObjects", request.ListOfObjects); err != nil { return err } diff --git a/seed/go-sdk/file-upload/snippet.json b/seed/go-sdk/file-upload/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/folders/.github/workflows/ci.yml b/seed/go-sdk/folders/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/folders/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/folders/a/b/client/client.go b/seed/go-sdk/folders/a/b/client/client.go new file mode 100644 index 00000000000..ba87a1ef9a8 --- /dev/null +++ b/seed/go-sdk/folders/a/b/client/client.go @@ -0,0 +1,62 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + core "github.com/folders/fern/core" + option "github.com/folders/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Foo( + ctx context.Context, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/folders/a/c/client/client.go b/seed/go-sdk/folders/a/c/client/client.go new file mode 100644 index 00000000000..ba87a1ef9a8 --- /dev/null +++ b/seed/go-sdk/folders/a/c/client/client.go @@ -0,0 +1,62 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + core "github.com/folders/fern/core" + option "github.com/folders/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Foo( + ctx context.Context, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/folders/a/client/client.go b/seed/go-sdk/folders/a/client/client.go new file mode 100644 index 00000000000..049d2389976 --- /dev/null +++ b/seed/go-sdk/folders/a/client/client.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + bclient "github.com/folders/fern/a/b/client" + cclient "github.com/folders/fern/a/c/client" + core "github.com/folders/fern/core" + option "github.com/folders/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + B *bclient.Client + C *cclient.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + B: bclient.NewClient(opts...), + C: cclient.NewClient(opts...), + } +} diff --git a/seed/go-sdk/folders/a/d/types.go b/seed/go-sdk/folders/a/d/types.go new file mode 100644 index 00000000000..7a258fa374d --- /dev/null +++ b/seed/go-sdk/folders/a/d/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package d + +type Foo = string diff --git a/seed/go-sdk/folders/client/client.go b/seed/go-sdk/folders/client/client.go new file mode 100644 index 00000000000..6f00b46896f --- /dev/null +++ b/seed/go-sdk/folders/client/client.go @@ -0,0 +1,69 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + aclient "github.com/folders/fern/a/client" + core "github.com/folders/fern/core" + folderclient "github.com/folders/fern/folder/client" + option "github.com/folders/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + A *aclient.Client + Folder *folderclient.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + A: aclient.NewClient(opts...), + Folder: folderclient.NewClient(opts...), + } +} + +func (c *Client) Foo( + ctx context.Context, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/folders/client/client_test.go b/seed/go-sdk/folders/client/client_test.go new file mode 100644 index 00000000000..59802845c6d --- /dev/null +++ b/seed/go-sdk/folders/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/folders/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/folders/core/core.go b/seed/go-sdk/folders/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/folders/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/folders/core/core_test.go b/seed/go-sdk/folders/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/folders/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/folders/core/query.go b/seed/go-sdk/folders/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/folders/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/folders/core/query_test.go b/seed/go-sdk/folders/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/folders/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/folders/core/request_option.go b/seed/go-sdk/folders/core/request_option.go new file mode 100644 index 00000000000..d95f2625667 --- /dev/null +++ b/seed/go-sdk/folders/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/folders/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/folders/core/retrier.go b/seed/go-sdk/folders/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/folders/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/folders/core/stringer.go b/seed/go-sdk/folders/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/folders/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/folders/core/time.go b/seed/go-sdk/folders/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/folders/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/folders/folder/client/client.go b/seed/go-sdk/folders/folder/client/client.go new file mode 100644 index 00000000000..b21887e61c0 --- /dev/null +++ b/seed/go-sdk/folders/folder/client/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + core "github.com/folders/fern/core" + service "github.com/folders/fern/folder/service" + option "github.com/folders/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} + +func (c *Client) Foo( + ctx context.Context, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/folders/folder/errors.go b/seed/go-sdk/folders/folder/errors.go new file mode 100644 index 00000000000..c9b3916d27e --- /dev/null +++ b/seed/go-sdk/folders/folder/errors.go @@ -0,0 +1,31 @@ +// This file was auto-generated by Fern from our API Definition. + +package folder + +import ( + json "encoding/json" + core "github.com/folders/fern/core" +) + +type NotFoundError struct { + *core.APIError + Body string +} + +func (n *NotFoundError) UnmarshalJSON(data []byte) error { + var body string + if err := json.Unmarshal(data, &body); err != nil { + return err + } + n.StatusCode = 404 + n.Body = body + return nil +} + +func (n *NotFoundError) MarshalJSON() ([]byte, error) { + return json.Marshal(n.Body) +} + +func (n *NotFoundError) Unwrap() error { + return n.APIError +} diff --git a/seed/go-sdk/folders/folder/service/client.go b/seed/go-sdk/folders/folder/service/client.go new file mode 100644 index 00000000000..84a3618603b --- /dev/null +++ b/seed/go-sdk/folders/folder/service/client.go @@ -0,0 +1,121 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + core "github.com/folders/fern/core" + folder "github.com/folders/fern/folder" + option "github.com/folders/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Endpoint( + ctx context.Context, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "service" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} + +func (c *Client) UnknownRequest( + ctx context.Context, + request interface{}, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "service" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 404: + value := new(folder.NotFoundError) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/folders/go.mod b/seed/go-sdk/folders/go.mod new file mode 100644 index 00000000000..dbb1f99416f --- /dev/null +++ b/seed/go-sdk/folders/go.mod @@ -0,0 +1,9 @@ +module github.com/folders/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/folders/go.sum b/seed/go-sdk/folders/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/folders/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/folders/option/request_option.go b/seed/go-sdk/folders/option/request_option.go new file mode 100644 index 00000000000..7c6621c6505 --- /dev/null +++ b/seed/go-sdk/folders/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/folders/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/folders/pointer.go b/seed/go-sdk/folders/pointer.go new file mode 100644 index 00000000000..faaf462e6d0 --- /dev/null +++ b/seed/go-sdk/folders/pointer.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/folders/snippet.json b/seed/go-sdk/folders/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/idempotency-headers/.github/workflows/ci.yml b/seed/go-sdk/idempotency-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/idempotency-headers/client/client.go b/seed/go-sdk/idempotency-headers/client/client.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/client/client.go rename to seed/go-sdk/idempotency-headers/client/client.go diff --git a/generators/go/seed/sdk/idempotency-headers/client/client_test.go b/seed/go-sdk/idempotency-headers/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/client/client_test.go rename to seed/go-sdk/idempotency-headers/client/client_test.go diff --git a/generators/go/seed/sdk/idempotency-headers/client/options.go b/seed/go-sdk/idempotency-headers/client/options.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/client/options.go rename to seed/go-sdk/idempotency-headers/client/options.go diff --git a/seed/go-sdk/idempotency-headers/core/core.go b/seed/go-sdk/idempotency-headers/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/idempotency-headers/core/core_test.go b/seed/go-sdk/idempotency-headers/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/generators/go/seed/sdk/idempotency-headers/core/idempotent_request_option.go b/seed/go-sdk/idempotency-headers/core/idempotent_request_option.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/idempotent_request_option.go rename to seed/go-sdk/idempotency-headers/core/idempotent_request_option.go diff --git a/seed/go-sdk/idempotency-headers/core/query.go b/seed/go-sdk/idempotency-headers/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/idempotency-headers/core/query_test.go b/seed/go-sdk/idempotency-headers/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/idempotency-headers/core/request_option.go b/seed/go-sdk/idempotency-headers/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/core/request_option.go rename to seed/go-sdk/idempotency-headers/core/request_option.go diff --git a/seed/go-sdk/idempotency-headers/core/retrier.go b/seed/go-sdk/idempotency-headers/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/idempotency-headers/core/stringer.go b/seed/go-sdk/idempotency-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/idempotency-headers/core/time.go b/seed/go-sdk/idempotency-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/idempotency-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/idempotency-headers/go.mod b/seed/go-sdk/idempotency-headers/go.mod similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/go.mod rename to seed/go-sdk/idempotency-headers/go.mod diff --git a/seed/go-sdk/idempotency-headers/go.sum b/seed/go-sdk/idempotency-headers/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/idempotency-headers/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/idempotency-headers/option/idempotent_request_option.go b/seed/go-sdk/idempotency-headers/option/idempotent_request_option.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/option/idempotent_request_option.go rename to seed/go-sdk/idempotency-headers/option/idempotent_request_option.go diff --git a/generators/go/seed/sdk/idempotency-headers/option/request_option.go b/seed/go-sdk/idempotency-headers/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/option/request_option.go rename to seed/go-sdk/idempotency-headers/option/request_option.go diff --git a/seed/go-sdk/idempotency-headers/payment.go b/seed/go-sdk/idempotency-headers/payment.go new file mode 100644 index 00000000000..b58b7283adf --- /dev/null +++ b/seed/go-sdk/idempotency-headers/payment.go @@ -0,0 +1,34 @@ +// This file was auto-generated by Fern from our API Definition. + +package fern + +import ( + fmt "fmt" +) + +type CreatePaymentRequest struct { + Amount int `json:"amount" url:"amount"` + Currency Currency `json:"currency,omitempty" url:"currency,omitempty"` +} + +type Currency string + +const ( + CurrencyUsd Currency = "USD" + CurrencyYen Currency = "YEN" +) + +func NewCurrencyFromString(s string) (Currency, error) { + switch s { + case "USD": + return CurrencyUsd, nil + case "YEN": + return CurrencyYen, nil + } + var t Currency + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c Currency) Ptr() *Currency { + return &c +} diff --git a/generators/go/seed/sdk/idempotency-headers/payment/client.go b/seed/go-sdk/idempotency-headers/payment/client.go similarity index 100% rename from generators/go/seed/sdk/idempotency-headers/payment/client.go rename to seed/go-sdk/idempotency-headers/payment/client.go diff --git a/generators/go/seed/sdk/idempotency-headers/pointer.go b/seed/go-sdk/idempotency-headers/pointer.go similarity index 75% rename from generators/go/seed/sdk/idempotency-headers/pointer.go rename to seed/go-sdk/idempotency-headers/pointer.go index 0fa1379aada..055bd616094 100644 --- a/generators/go/seed/sdk/idempotency-headers/pointer.go +++ b/seed/go-sdk/idempotency-headers/pointer.go @@ -1,6 +1,10 @@ package fern -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/idempotency-headers/snippet.json b/seed/go-sdk/idempotency-headers/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/imdb/.github/workflows/ci.yml b/seed/go-sdk/imdb/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/imdb/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/imdb/client/client.go b/seed/go-sdk/imdb/client/client.go new file mode 100644 index 00000000000..70b11c873a6 --- /dev/null +++ b/seed/go-sdk/imdb/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/imdb/fern/core" + imdb "github.com/imdb/fern/imdb" + option "github.com/imdb/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Imdb *imdb.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Imdb: imdb.NewClient(opts...), + } +} diff --git a/seed/go-sdk/imdb/client/client_test.go b/seed/go-sdk/imdb/client/client_test.go new file mode 100644 index 00000000000..f8f37485940 --- /dev/null +++ b/seed/go-sdk/imdb/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/imdb/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/imdb/core/core.go b/seed/go-sdk/imdb/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/imdb/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/imdb/core/core_test.go b/seed/go-sdk/imdb/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/imdb/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/imdb/core/query.go b/seed/go-sdk/imdb/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/imdb/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/imdb/core/query_test.go b/seed/go-sdk/imdb/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/imdb/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/imdb/core/request_option.go b/seed/go-sdk/imdb/core/request_option.go new file mode 100644 index 00000000000..dd69b52966b --- /dev/null +++ b/seed/go-sdk/imdb/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/imdb/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/imdb/core/retrier.go b/seed/go-sdk/imdb/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/imdb/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/imdb/core/stringer.go b/seed/go-sdk/imdb/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/imdb/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/imdb/core/time.go b/seed/go-sdk/imdb/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/imdb/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/imdb/errors.go b/seed/go-sdk/imdb/errors.go new file mode 100644 index 00000000000..347831998af --- /dev/null +++ b/seed/go-sdk/imdb/errors.go @@ -0,0 +1,31 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + core "github.com/imdb/fern/core" +) + +type MovieDoesNotExistError struct { + *core.APIError + Body MovieId +} + +func (m *MovieDoesNotExistError) UnmarshalJSON(data []byte) error { + var body MovieId + if err := json.Unmarshal(data, &body); err != nil { + return err + } + m.StatusCode = 404 + m.Body = body + return nil +} + +func (m *MovieDoesNotExistError) MarshalJSON() ([]byte, error) { + return json.Marshal(m.Body) +} + +func (m *MovieDoesNotExistError) Unwrap() error { + return m.APIError +} diff --git a/seed/go-sdk/imdb/go.mod b/seed/go-sdk/imdb/go.mod new file mode 100644 index 00000000000..4ae3d369ddb --- /dev/null +++ b/seed/go-sdk/imdb/go.mod @@ -0,0 +1,9 @@ +module github.com/imdb/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/imdb/go.sum b/seed/go-sdk/imdb/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/imdb/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/imdb/imdb.go b/seed/go-sdk/imdb/imdb.go new file mode 100644 index 00000000000..838657595d1 --- /dev/null +++ b/seed/go-sdk/imdb/imdb.go @@ -0,0 +1,73 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/imdb/fern/core" +) + +type CreateMovieRequest struct { + Title string `json:"title" url:"title"` + Rating float64 `json:"rating" url:"rating"` + + _rawJSON json.RawMessage +} + +func (c *CreateMovieRequest) UnmarshalJSON(data []byte) error { + type unmarshaler CreateMovieRequest + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateMovieRequest(value) + c._rawJSON = json.RawMessage(data) + return nil +} + +func (c *CreateMovieRequest) String() string { + if len(c._rawJSON) > 0 { + if value, err := core.StringifyJSON(c._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Title string `json:"title" url:"title"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + + _rawJSON json.RawMessage +} + +func (m *Movie) UnmarshalJSON(data []byte) error { + type unmarshaler Movie + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Movie(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Movie) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string diff --git a/seed/go-sdk/imdb/imdb/client.go b/seed/go-sdk/imdb/imdb/client.go new file mode 100644 index 00000000000..e4f3099f682 --- /dev/null +++ b/seed/go-sdk/imdb/imdb/client.go @@ -0,0 +1,128 @@ +// This file was auto-generated by Fern from our API Definition. + +package imdb + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fmt "fmt" + fern "github.com/imdb/fern" + core "github.com/imdb/fern/core" + option "github.com/imdb/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// Add a movie to the database +func (c *Client) CreateMovie( + ctx context.Context, + request *fern.CreateMovieRequest, + opts ...option.RequestOption, +) (fern.MovieId, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "movies/create-movie" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response fern.MovieId + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} + +func (c *Client) GetMovie( + ctx context.Context, + movieId fern.MovieId, + opts ...option.RequestOption, +) (*fern.Movie, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"movies/%v", movieId) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 404: + value := new(fern.MovieDoesNotExistError) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response *fern.Movie + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/imdb/option/request_option.go b/seed/go-sdk/imdb/option/request_option.go new file mode 100644 index 00000000000..a0f84e60934 --- /dev/null +++ b/seed/go-sdk/imdb/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/imdb/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/imdb/pointer.go b/seed/go-sdk/imdb/pointer.go new file mode 100644 index 00000000000..faaf462e6d0 --- /dev/null +++ b/seed/go-sdk/imdb/pointer.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/imdb/snippet.json b/seed/go-sdk/imdb/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/literal-headers/.github/workflows/ci.yml b/seed/go-sdk/literal-headers/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/literal-headers/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/literal-headers/client/client.go b/seed/go-sdk/literal-headers/client/client.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/client/client.go rename to seed/go-sdk/literal-headers/client/client.go diff --git a/generators/go/seed/sdk/literal-headers/client/client_test.go b/seed/go-sdk/literal-headers/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/client/client_test.go rename to seed/go-sdk/literal-headers/client/client_test.go diff --git a/seed/go-sdk/literal-headers/core/core.go b/seed/go-sdk/literal-headers/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/literal-headers/core/core_test.go b/seed/go-sdk/literal-headers/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/literal-headers/core/query.go b/seed/go-sdk/literal-headers/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/literal-headers/core/query_test.go b/seed/go-sdk/literal-headers/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/literal-headers/core/request_option.go b/seed/go-sdk/literal-headers/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/core/request_option.go rename to seed/go-sdk/literal-headers/core/request_option.go diff --git a/seed/go-sdk/literal-headers/core/retrier.go b/seed/go-sdk/literal-headers/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/literal-headers/core/stringer.go b/seed/go-sdk/literal-headers/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/literal-headers/core/time.go b/seed/go-sdk/literal-headers/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/literal-headers/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/literal-headers/go.mod b/seed/go-sdk/literal-headers/go.mod similarity index 100% rename from generators/go/seed/sdk/literal-headers/go.mod rename to seed/go-sdk/literal-headers/go.mod diff --git a/seed/go-sdk/literal-headers/go.sum b/seed/go-sdk/literal-headers/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/literal-headers/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/literal-headers/noheaders/client.go b/seed/go-sdk/literal-headers/noheaders/client.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/noheaders/client.go rename to seed/go-sdk/literal-headers/noheaders/client.go diff --git a/generators/go/seed/sdk/literal-headers/onlyliteralheaders/client.go b/seed/go-sdk/literal-headers/onlyliteralheaders/client.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/onlyliteralheaders/client.go rename to seed/go-sdk/literal-headers/onlyliteralheaders/client.go diff --git a/generators/go/seed/sdk/literal-headers/option/request_option.go b/seed/go-sdk/literal-headers/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/option/request_option.go rename to seed/go-sdk/literal-headers/option/request_option.go diff --git a/generators/go/seed/sdk/literal-headers/pointer.go b/seed/go-sdk/literal-headers/pointer.go similarity index 75% rename from generators/go/seed/sdk/literal-headers/pointer.go rename to seed/go-sdk/literal-headers/pointer.go index fb2fbcb92ee..eda5e48d942 100644 --- a/generators/go/seed/sdk/literal-headers/pointer.go +++ b/seed/go-sdk/literal-headers/pointer.go @@ -1,6 +1,10 @@ package literalheaders -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/literal-headers/snippet.json b/seed/go-sdk/literal-headers/snippet.json new file mode 100644 index 00000000000..95af74f2d4e --- /dev/null +++ b/seed/go-sdk/literal-headers/snippet.json @@ -0,0 +1,24 @@ +{ + "endpoints": [ + { + "id": { + "path": "/only-literal-headers", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/literal-headers/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.OnlyLiteralHeaders.Get(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/with-non-literal-headers", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/literal-headers/fern\"\n\tfernclient \"github.com/literal-headers/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.WithNonLiteralHeaders.Get(\n\tcontext.TODO(),\n\t\u0026fern.WithNonLiteralHeadersRequest{\n\t\tInteger: 42,\n\t\tLiteralServiceHeader: \"service header\",\n\t\tTrueServiceHeader: true,\n\t\tNonLiteralEndpointHeader: \"custom header\",\n\t},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/generators/go/seed/sdk/literal-headers/with_non_literal_headers.go b/seed/go-sdk/literal-headers/with_non_literal_headers.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/with_non_literal_headers.go rename to seed/go-sdk/literal-headers/with_non_literal_headers.go diff --git a/generators/go/seed/sdk/literal-headers/withnonliteralheaders/client.go b/seed/go-sdk/literal-headers/withnonliteralheaders/client.go similarity index 100% rename from generators/go/seed/sdk/literal-headers/withnonliteralheaders/client.go rename to seed/go-sdk/literal-headers/withnonliteralheaders/client.go diff --git a/seed/go-sdk/literal/.github/workflows/ci.yml b/seed/go-sdk/literal/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/literal/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/literal/client/client.go b/seed/go-sdk/literal/client/client.go new file mode 100644 index 00000000000..4586b09d259 --- /dev/null +++ b/seed/go-sdk/literal/client/client.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/literal/fern/core" + headers "github.com/literal/fern/headers" + inlined "github.com/literal/fern/inlined" + option "github.com/literal/fern/option" + path "github.com/literal/fern/path" + query "github.com/literal/fern/query" + reference "github.com/literal/fern/reference" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Headers *headers.Client + Inlined *inlined.Client + Path *path.Client + Query *query.Client + Reference *reference.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Headers: headers.NewClient(opts...), + Inlined: inlined.NewClient(opts...), + Path: path.NewClient(opts...), + Query: query.NewClient(opts...), + Reference: reference.NewClient(opts...), + } +} diff --git a/generators/go/seed/sdk/literal/client/client_test.go b/seed/go-sdk/literal/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/literal/client/client_test.go rename to seed/go-sdk/literal/client/client_test.go diff --git a/seed/go-sdk/literal/core/core.go b/seed/go-sdk/literal/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/literal/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/literal/core/core_test.go b/seed/go-sdk/literal/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/literal/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/literal/core/query.go b/seed/go-sdk/literal/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/literal/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/literal/core/query_test.go b/seed/go-sdk/literal/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/literal/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/literal/core/request_option.go b/seed/go-sdk/literal/core/request_option.go new file mode 100644 index 00000000000..87aa0f14126 --- /dev/null +++ b/seed/go-sdk/literal/core/request_option.go @@ -0,0 +1,91 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + fmt "fmt" + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + header.Set("X-API-Version", fmt.Sprintf("%v", "02-02-2024")) + header.Set("X-API-Enable-Audit-Logging", fmt.Sprintf("%v", true)) + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/literal/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/literal/core/retrier.go b/seed/go-sdk/literal/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/literal/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/literal/core/stringer.go b/seed/go-sdk/literal/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/literal/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/literal/core/time.go b/seed/go-sdk/literal/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/literal/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/literal/go.mod b/seed/go-sdk/literal/go.mod similarity index 100% rename from generators/go/seed/sdk/literal/go.mod rename to seed/go-sdk/literal/go.mod diff --git a/seed/go-sdk/literal/go.sum b/seed/go-sdk/literal/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/literal/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/literal/headers.go b/seed/go-sdk/literal/headers.go new file mode 100644 index 00000000000..28d7855834a --- /dev/null +++ b/seed/go-sdk/literal/headers.go @@ -0,0 +1,47 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" +) + +type SendLiteralsInHeadersRequest struct { + Query string `json:"query" url:"query"` + endpointVersion string + async bool +} + +func (s *SendLiteralsInHeadersRequest) EndpointVersion() string { + return s.endpointVersion +} + +func (s *SendLiteralsInHeadersRequest) Async() bool { + return s.async +} + +func (s *SendLiteralsInHeadersRequest) UnmarshalJSON(data []byte) error { + type unmarshaler SendLiteralsInHeadersRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *s = SendLiteralsInHeadersRequest(body) + s.endpointVersion = "02-12-2024" + s.async = true + return nil +} + +func (s *SendLiteralsInHeadersRequest) MarshalJSON() ([]byte, error) { + type embed SendLiteralsInHeadersRequest + var marshaler = struct { + embed + EndpointVersion string `json:"X-Endpoint-Version"` + Async bool `json:"X-Async"` + }{ + embed: embed(*s), + EndpointVersion: "02-12-2024", + Async: true, + } + return json.Marshal(marshaler) +} diff --git a/seed/go-sdk/literal/headers/client.go b/seed/go-sdk/literal/headers/client.go new file mode 100644 index 00000000000..0a1d4d10bf0 --- /dev/null +++ b/seed/go-sdk/literal/headers/client.go @@ -0,0 +1,70 @@ +// This file was auto-generated by Fern from our API Definition. + +package headers + +import ( + context "context" + fmt "fmt" + fern "github.com/literal/fern" + core "github.com/literal/fern/core" + option "github.com/literal/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Send( + ctx context.Context, + request *fern.SendLiteralsInHeadersRequest, + opts ...option.RequestOption, +) (*fern.SendResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "headers" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers.Add("X-Endpoint-Version", fmt.Sprintf("%v", "02-12-2024")) + headers.Add("X-Async", fmt.Sprintf("%v", true)) + + var response *fern.SendResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/literal/inlined.go b/seed/go-sdk/literal/inlined.go new file mode 100644 index 00000000000..d81f4f0b357 --- /dev/null +++ b/seed/go-sdk/literal/inlined.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" +) + +type SendLiteralsInlinedRequest struct { + Query string `json:"query" url:"query"` + Temperature *float64 `json:"temperature,omitempty" url:"temperature,omitempty"` + prompt string + stream bool +} + +func (s *SendLiteralsInlinedRequest) Prompt() string { + return s.prompt +} + +func (s *SendLiteralsInlinedRequest) Stream() bool { + return s.stream +} + +func (s *SendLiteralsInlinedRequest) UnmarshalJSON(data []byte) error { + type unmarshaler SendLiteralsInlinedRequest + var body unmarshaler + if err := json.Unmarshal(data, &body); err != nil { + return err + } + *s = SendLiteralsInlinedRequest(body) + s.prompt = "You are a helpful assistant" + s.stream = false + return nil +} + +func (s *SendLiteralsInlinedRequest) MarshalJSON() ([]byte, error) { + type embed SendLiteralsInlinedRequest + var marshaler = struct { + embed + Prompt string `json:"prompt"` + Stream bool `json:"stream"` + }{ + embed: embed(*s), + Prompt: "You are a helpful assistant", + Stream: false, + } + return json.Marshal(marshaler) +} diff --git a/seed/go-sdk/literal/inlined/client.go b/seed/go-sdk/literal/inlined/client.go new file mode 100644 index 00000000000..51cf8174ccc --- /dev/null +++ b/seed/go-sdk/literal/inlined/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package inlined + +import ( + context "context" + fern "github.com/literal/fern" + core "github.com/literal/fern/core" + option "github.com/literal/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Send( + ctx context.Context, + request *fern.SendLiteralsInlinedRequest, + opts ...option.RequestOption, +) (*fern.SendResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "inlined" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.SendResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/generators/go/seed/sdk/literal/option/request_option.go b/seed/go-sdk/literal/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/literal/option/request_option.go rename to seed/go-sdk/literal/option/request_option.go diff --git a/seed/go-sdk/literal/path/client.go b/seed/go-sdk/literal/path/client.go new file mode 100644 index 00000000000..b32334ad025 --- /dev/null +++ b/seed/go-sdk/literal/path/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package path + +import ( + context "context" + fmt "fmt" + fern "github.com/literal/fern" + core "github.com/literal/fern/core" + option "github.com/literal/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Send( + ctx context.Context, + id string, + opts ...option.RequestOption, +) (*fern.SendResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"path/%v", id) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.SendResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/generators/go/seed/sdk/literal/pointer.go b/seed/go-sdk/literal/pointer.go similarity index 75% rename from generators/go/seed/sdk/literal/pointer.go rename to seed/go-sdk/literal/pointer.go index a685a73c6fa..21e07f5d116 100644 --- a/generators/go/seed/sdk/literal/pointer.go +++ b/seed/go-sdk/literal/pointer.go @@ -1,6 +1,10 @@ package literal -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/literal/query.go b/seed/go-sdk/literal/query.go new file mode 100644 index 00000000000..bc09837d487 --- /dev/null +++ b/seed/go-sdk/literal/query.go @@ -0,0 +1,17 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +type SendLiteralsInQueryRequest struct { + Query string `json:"-" url:"query"` + prompt string + stream bool +} + +func (s *SendLiteralsInQueryRequest) Prompt() string { + return s.prompt +} + +func (s *SendLiteralsInQueryRequest) Stream() bool { + return s.stream +} diff --git a/seed/go-sdk/literal/query/client.go b/seed/go-sdk/literal/query/client.go new file mode 100644 index 00000000000..b474ec5a996 --- /dev/null +++ b/seed/go-sdk/literal/query/client.go @@ -0,0 +1,77 @@ +// This file was auto-generated by Fern from our API Definition. + +package query + +import ( + context "context" + fmt "fmt" + fern "github.com/literal/fern" + core "github.com/literal/fern/core" + option "github.com/literal/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Send( + ctx context.Context, + request *fern.SendLiteralsInQueryRequest, + opts ...option.RequestOption, +) (*fern.SendResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "query" + + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err + } + queryParams.Add("prompt", fmt.Sprintf("%v", "You are a helpful assistant")) + queryParams.Add("stream", fmt.Sprintf("%v", false)) + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.SendResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/literal/reference.go b/seed/go-sdk/literal/reference.go new file mode 100644 index 00000000000..1596ad84ac9 --- /dev/null +++ b/seed/go-sdk/literal/reference.go @@ -0,0 +1,68 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendRequest struct { + Query string `json:"query" url:"query"` + prompt string + stream bool + + _rawJSON json.RawMessage +} + +func (s *SendRequest) Prompt() string { + return s.prompt +} + +func (s *SendRequest) Stream() bool { + return s.stream +} + +func (s *SendRequest) UnmarshalJSON(data []byte) error { + type embed SendRequest + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendRequest(unmarshaler.embed) + s.prompt = "You are a helpful assistant" + s.stream = false + s._rawJSON = json.RawMessage(data) + return nil +} + +func (s *SendRequest) MarshalJSON() ([]byte, error) { + type embed SendRequest + var marshaler = struct { + embed + Prompt string `json:"prompt"` + Stream bool `json:"stream"` + }{ + embed: embed(*s), + Prompt: "You are a helpful assistant", + Stream: false, + } + return json.Marshal(marshaler) +} + +func (s *SendRequest) String() string { + if len(s._rawJSON) > 0 { + if value, err := core.StringifyJSON(s._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-sdk/literal/reference/client.go b/seed/go-sdk/literal/reference/client.go new file mode 100644 index 00000000000..e926eba9f50 --- /dev/null +++ b/seed/go-sdk/literal/reference/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package reference + +import ( + context "context" + fern "github.com/literal/fern" + core "github.com/literal/fern/core" + option "github.com/literal/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Send( + ctx context.Context, + request *fern.SendRequest, + opts ...option.RequestOption, +) (*fern.SendResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "reference" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.SendResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/literal/snippet.json b/seed/go-sdk/literal/snippet.json new file mode 100644 index 00000000000..8e3059ea5b0 --- /dev/null +++ b/seed/go-sdk/literal/snippet.json @@ -0,0 +1,54 @@ +{ + "endpoints": [ + { + "id": { + "path": "/headers", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/literal/fern\"\n\tfernclient \"github.com/literal/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Headers.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendLiteralsInHeadersRequest{\n\t\tQuery: \"What is the weather today\",\n\t},\n)\n" + } + }, + { + "id": { + "path": "/inlined", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/literal/fern\"\n\tfernclient \"github.com/literal/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Inlined.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendLiteralsInlinedRequest{\n\t\tTemperature: fern.Float64(\n\t\t\t10.1,\n\t\t),\n\t},\n)\n" + } + }, + { + "id": { + "path": "/path/{id}", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/literal/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Path.Send(\n\tcontext.TODO(),\n\t\"123\",\n)\n" + } + }, + { + "id": { + "path": "/query", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/literal/fern\"\n\tfernclient \"github.com/literal/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Query.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendLiteralsInQueryRequest{\n\t\tQuery: \"What is the weather today\",\n\t},\n)\n" + } + }, + { + "id": { + "path": "/reference", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/literal/fern\"\n\tfernclient \"github.com/literal/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Reference.Send(\n\tcontext.TODO(),\n\t\u0026fern.SendRequest{\n\t\tQuery: \"What is the weather today\",\n\t},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/literal/types.go b/seed/go-sdk/literal/types.go new file mode 100644 index 00000000000..3d893373f3f --- /dev/null +++ b/seed/go-sdk/literal/types.go @@ -0,0 +1,61 @@ +// This file was auto-generated by Fern from our API Definition. + +package literal + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/literal/fern/core" +) + +type SendResponse struct { + Message string `json:"message" url:"message"` + Status int `json:"status" url:"status"` + success bool + + _rawJSON json.RawMessage +} + +func (s *SendResponse) Success() bool { + return s.success +} + +func (s *SendResponse) UnmarshalJSON(data []byte) error { + type embed SendResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *s = SendResponse(unmarshaler.embed) + s.success = true + s._rawJSON = json.RawMessage(data) + return nil +} + +func (s *SendResponse) MarshalJSON() ([]byte, error) { + type embed SendResponse + var marshaler = struct { + embed + Success bool `json:"success"` + }{ + embed: embed(*s), + Success: true, + } + return json.Marshal(marshaler) +} + +func (s *SendResponse) String() string { + if len(s._rawJSON) > 0 { + if value, err := core.StringifyJSON(s._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} diff --git a/seed/go-sdk/multi-url-environment/.github/workflows/ci.yml b/seed/go-sdk/multi-url-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/multi-url-environment/client/client.go b/seed/go-sdk/multi-url-environment/client/client.go new file mode 100644 index 00000000000..eb955497699 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/client/client.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/multi-url-environment/fern/core" + ec2 "github.com/multi-url-environment/fern/ec2" + option "github.com/multi-url-environment/fern/option" + s3 "github.com/multi-url-environment/fern/s3" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Ec2 *ec2.Client + S3 *s3.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Ec2: ec2.NewClient(opts...), + S3: s3.NewClient(opts...), + } +} diff --git a/seed/go-sdk/multi-url-environment/client/client_test.go b/seed/go-sdk/multi-url-environment/client/client_test.go new file mode 100644 index 00000000000..9a9bab02787 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/multi-url-environment/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/multi-url-environment/core/core.go b/seed/go-sdk/multi-url-environment/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/multi-url-environment/core/core_test.go b/seed/go-sdk/multi-url-environment/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/multi-url-environment/core/query.go b/seed/go-sdk/multi-url-environment/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/multi-url-environment/core/query_test.go b/seed/go-sdk/multi-url-environment/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/multi-url-environment/core/request_option.go b/seed/go-sdk/multi-url-environment/core/request_option.go new file mode 100644 index 00000000000..76041cce55b --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/multi-url-environment/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/multi-url-environment/core/retrier.go b/seed/go-sdk/multi-url-environment/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/multi-url-environment/core/stringer.go b/seed/go-sdk/multi-url-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/multi-url-environment/core/time.go b/seed/go-sdk/multi-url-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/multi-url-environment/ec2/client.go b/seed/go-sdk/multi-url-environment/ec2/client.go new file mode 100644 index 00000000000..b8291dab8d1 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/ec2/client.go @@ -0,0 +1,65 @@ +// This file was auto-generated by Fern from our API Definition. + +package ec2 + +import ( + context "context" + fern "github.com/multi-url-environment/fern" + core "github.com/multi-url-environment/fern/core" + option "github.com/multi-url-environment/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) BootInstance( + ctx context.Context, + request *fern.BootInstanceRequest, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "https://ec2.aws.com" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "ec2/boot" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/multi-url-environment/ec_2.go b/seed/go-sdk/multi-url-environment/ec_2.go new file mode 100644 index 00000000000..96e1778fd6b --- /dev/null +++ b/seed/go-sdk/multi-url-environment/ec_2.go @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment + +type BootInstanceRequest struct { + Size string `json:"size" url:"size"` +} diff --git a/seed/go-sdk/multi-url-environment/environments.go b/seed/go-sdk/multi-url-environment/environments.go new file mode 100644 index 00000000000..22467a4f844 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/environments.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Production struct { + Ec2 string + S3 string + } + Staging struct { + Ec2 string + S3 string + } +}{ + Production: struct { + Ec2 string + S3 string + }{ + Ec2: "https://ec2.aws.com", + S3: "https://s3.aws.com", + }, + Staging: struct { + Ec2 string + S3 string + }{ + Ec2: "https://staging.ec2.aws.com", + S3: "https://staging.s3.aws.com", + }, +} diff --git a/seed/go-sdk/multi-url-environment/go.mod b/seed/go-sdk/multi-url-environment/go.mod new file mode 100644 index 00000000000..d08e7a1dc4d --- /dev/null +++ b/seed/go-sdk/multi-url-environment/go.mod @@ -0,0 +1,9 @@ +module github.com/multi-url-environment/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/multi-url-environment/go.sum b/seed/go-sdk/multi-url-environment/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/multi-url-environment/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/multi-url-environment/option/request_option.go b/seed/go-sdk/multi-url-environment/option/request_option.go new file mode 100644 index 00000000000..b0adf4f3bb4 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/multi-url-environment/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/multi-url-environment/pointer.go b/seed/go-sdk/multi-url-environment/pointer.go new file mode 100644 index 00000000000..85523f61c71 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/pointer.go @@ -0,0 +1,132 @@ +package multiurlenvironment + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/multi-url-environment/s3/client.go b/seed/go-sdk/multi-url-environment/s3/client.go new file mode 100644 index 00000000000..919117ccf83 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/s3/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package s3 + +import ( + context "context" + fern "github.com/multi-url-environment/fern" + core "github.com/multi-url-environment/fern/core" + option "github.com/multi-url-environment/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetPresignedUrl( + ctx context.Context, + request *fern.GetPresignedUrlRequest, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "https://s3.aws.com" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "s3/presigned-url" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/multi-url-environment/s_3.go b/seed/go-sdk/multi-url-environment/s_3.go new file mode 100644 index 00000000000..906e165e9d2 --- /dev/null +++ b/seed/go-sdk/multi-url-environment/s_3.go @@ -0,0 +1,7 @@ +// This file was auto-generated by Fern from our API Definition. + +package multiurlenvironment + +type GetPresignedUrlRequest struct { + S3Key string `json:"s3Key" url:"s3Key"` +} diff --git a/seed/go-sdk/multi-url-environment/snippet.json b/seed/go-sdk/multi-url-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/no-environment/.github/workflows/ci.yml b/seed/go-sdk/no-environment/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/no-environment/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/no-environment/client/client.go b/seed/go-sdk/no-environment/client/client.go new file mode 100644 index 00000000000..10a7910febb --- /dev/null +++ b/seed/go-sdk/no-environment/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/no-environment/fern/core" + dummy "github.com/no-environment/fern/dummy" + option "github.com/no-environment/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Dummy *dummy.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Dummy: dummy.NewClient(opts...), + } +} diff --git a/seed/go-sdk/no-environment/client/client_test.go b/seed/go-sdk/no-environment/client/client_test.go new file mode 100644 index 00000000000..061656a5a89 --- /dev/null +++ b/seed/go-sdk/no-environment/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/no-environment/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/no-environment/core/core.go b/seed/go-sdk/no-environment/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/no-environment/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/no-environment/core/core_test.go b/seed/go-sdk/no-environment/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/no-environment/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/no-environment/core/query.go b/seed/go-sdk/no-environment/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/no-environment/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/no-environment/core/query_test.go b/seed/go-sdk/no-environment/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/no-environment/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/no-environment/core/request_option.go b/seed/go-sdk/no-environment/core/request_option.go new file mode 100644 index 00000000000..9ec99c0a13c --- /dev/null +++ b/seed/go-sdk/no-environment/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/no-environment/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/no-environment/core/retrier.go b/seed/go-sdk/no-environment/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/no-environment/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/no-environment/core/stringer.go b/seed/go-sdk/no-environment/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/no-environment/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/no-environment/core/time.go b/seed/go-sdk/no-environment/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/no-environment/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/no-environment/dummy/client.go b/seed/go-sdk/no-environment/dummy/client.go new file mode 100644 index 00000000000..9ed89735456 --- /dev/null +++ b/seed/go-sdk/no-environment/dummy/client.go @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +package dummy + +import ( + context "context" + core "github.com/no-environment/fern/core" + option "github.com/no-environment/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetDummy( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "dummy" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/no-environment/go.mod b/seed/go-sdk/no-environment/go.mod new file mode 100644 index 00000000000..26365a6dd34 --- /dev/null +++ b/seed/go-sdk/no-environment/go.mod @@ -0,0 +1,9 @@ +module github.com/no-environment/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/no-environment/go.sum b/seed/go-sdk/no-environment/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/no-environment/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/no-environment/option/request_option.go b/seed/go-sdk/no-environment/option/request_option.go new file mode 100644 index 00000000000..9beae1f47ed --- /dev/null +++ b/seed/go-sdk/no-environment/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/no-environment/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/no-environment/pointer.go b/seed/go-sdk/no-environment/pointer.go new file mode 100644 index 00000000000..730c58d4a51 --- /dev/null +++ b/seed/go-sdk/no-environment/pointer.go @@ -0,0 +1,132 @@ +package noenvironment + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/no-environment/snippet.json b/seed/go-sdk/no-environment/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/object/.github/workflows/ci.yml b/seed/go-sdk/object/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/object/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/object/client/client.go b/seed/go-sdk/object/client/client.go new file mode 100644 index 00000000000..134697536a5 --- /dev/null +++ b/seed/go-sdk/object/client/client.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/object/fern/core" + option "github.com/object/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} diff --git a/seed/go-sdk/object/client/client_test.go b/seed/go-sdk/object/client/client_test.go new file mode 100644 index 00000000000..e5f23cd96f8 --- /dev/null +++ b/seed/go-sdk/object/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/object/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/object/core/core.go b/seed/go-sdk/object/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/object/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/object/core/core_test.go b/seed/go-sdk/object/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/object/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/object/core/query.go b/seed/go-sdk/object/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/object/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/object/core/query_test.go b/seed/go-sdk/object/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/object/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/object/core/request_option.go b/seed/go-sdk/object/core/request_option.go new file mode 100644 index 00000000000..f2a7b29e232 --- /dev/null +++ b/seed/go-sdk/object/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/object/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/object/core/retrier.go b/seed/go-sdk/object/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/object/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/object/core/stringer.go b/seed/go-sdk/object/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/object/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/object/core/time.go b/seed/go-sdk/object/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/object/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/object/go.mod b/seed/go-sdk/object/go.mod new file mode 100644 index 00000000000..23e902b0a6f --- /dev/null +++ b/seed/go-sdk/object/go.mod @@ -0,0 +1,9 @@ +module github.com/object/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/object/go.sum b/seed/go-sdk/object/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/object/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/object/option/request_option.go b/seed/go-sdk/object/option/request_option.go new file mode 100644 index 00000000000..8665531ce55 --- /dev/null +++ b/seed/go-sdk/object/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/object/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/object/pointer.go b/seed/go-sdk/object/pointer.go new file mode 100644 index 00000000000..d7fa5bf11bb --- /dev/null +++ b/seed/go-sdk/object/pointer.go @@ -0,0 +1,132 @@ +package object + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/object/snippet.json b/seed/go-sdk/object/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/object/types.go b/seed/go-sdk/object/types.go new file mode 100644 index 00000000000..94a7cb18059 --- /dev/null +++ b/seed/go-sdk/object/types.go @@ -0,0 +1,118 @@ +// This file was auto-generated by Fern from our API Definition. + +package object + +import ( + json "encoding/json" + fmt "fmt" + uuid "github.com/google/uuid" + core "github.com/object/fern/core" + time "time" +) + +type Name struct { + Id string `json:"id" url:"id"` + Value string `json:"value" url:"value"` + + _rawJSON json.RawMessage +} + +func (n *Name) UnmarshalJSON(data []byte) error { + type unmarshaler Name + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *n = Name(value) + n._rawJSON = json.RawMessage(data) + return nil +} + +func (n *Name) String() string { + if len(n._rawJSON) > 0 { + if value, err := core.StringifyJSON(n._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +// Exercises all of the built-in types. +type Type struct { + One int `json:"one" url:"one"` + Two float64 `json:"two" url:"two"` + Three string `json:"three" url:"three"` + Four bool `json:"four" url:"four"` + Five int64 `json:"five" url:"five"` + Six time.Time `json:"six" url:"six"` + Seven time.Time `json:"seven" url:"seven" format:"date"` + Eight uuid.UUID `json:"eight" url:"eight"` + Nine []byte `json:"nine" url:"nine"` + Ten []int `json:"ten,omitempty" url:"ten,omitempty"` + Eleven []float64 `json:"eleven,omitempty" url:"eleven,omitempty"` + Twelve map[string]bool `json:"twelve,omitempty" url:"twelve,omitempty"` + Thirteen *int64 `json:"thirteen,omitempty" url:"thirteen,omitempty"` + Fourteen interface{} `json:"fourteen,omitempty" url:"fourteen,omitempty"` + Fifteen [][]int `json:"fifteen,omitempty" url:"fifteen,omitempty"` + Sixteen []map[string]int `json:"sixteen,omitempty" url:"sixteen,omitempty"` + Seventeen []*uuid.UUID `json:"seventeen,omitempty" url:"seventeen,omitempty"` + Nineteen *Name `json:"nineteen,omitempty" url:"nineteen,omitempty"` + eighteen string + + _rawJSON json.RawMessage +} + +func (t *Type) Eighteen() string { + return t.eighteen +} + +func (t *Type) UnmarshalJSON(data []byte) error { + type embed Type + var unmarshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + }{ + embed: embed(*t), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *t = Type(unmarshaler.embed) + t.Six = unmarshaler.Six.Time() + t.Seven = unmarshaler.Seven.Time() + t.eighteen = "eighteen" + t._rawJSON = json.RawMessage(data) + return nil +} + +func (t *Type) MarshalJSON() ([]byte, error) { + type embed Type + var marshaler = struct { + embed + Six *core.DateTime `json:"six"` + Seven *core.Date `json:"seven"` + Eighteen string `json:"eighteen"` + }{ + embed: embed(*t), + Six: core.NewDateTime(t.Six), + Seven: core.NewDate(t.Seven), + Eighteen: "eighteen", + } + return json.Marshal(marshaler) +} + +func (t *Type) String() string { + if len(t._rawJSON) > 0 { + if value, err := core.StringifyJSON(t._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-sdk/objects-with-imports/.github/workflows/ci.yml b/seed/go-sdk/objects-with-imports/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/objects-with-imports/client/client.go b/seed/go-sdk/objects-with-imports/client/client.go new file mode 100644 index 00000000000..791508d694a --- /dev/null +++ b/seed/go-sdk/objects-with-imports/client/client.go @@ -0,0 +1,29 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/objects-with-imports/fern/core" + option "github.com/objects-with-imports/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} diff --git a/seed/go-sdk/objects-with-imports/client/client_test.go b/seed/go-sdk/objects-with-imports/client/client_test.go new file mode 100644 index 00000000000..db7e9e8ec41 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/objects-with-imports/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/objects-with-imports/commons/types.go b/seed/go-sdk/objects-with-imports/commons/types.go new file mode 100644 index 00000000000..3ef825365a0 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/commons/types.go @@ -0,0 +1,39 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/objects-with-imports/fern/core" +) + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` + + _rawJSON json.RawMessage +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + type unmarshaler Metadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Metadata(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Metadata) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-sdk/objects-with-imports/core/core.go b/seed/go-sdk/objects-with-imports/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/objects-with-imports/core/core_test.go b/seed/go-sdk/objects-with-imports/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/objects-with-imports/core/query.go b/seed/go-sdk/objects-with-imports/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/objects-with-imports/core/query_test.go b/seed/go-sdk/objects-with-imports/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/objects-with-imports/core/request_option.go b/seed/go-sdk/objects-with-imports/core/request_option.go new file mode 100644 index 00000000000..4fbe47daf14 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/objects-with-imports/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/objects-with-imports/core/retrier.go b/seed/go-sdk/objects-with-imports/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/objects-with-imports/core/stringer.go b/seed/go-sdk/objects-with-imports/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/objects-with-imports/core/time.go b/seed/go-sdk/objects-with-imports/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/objects-with-imports/file/types.go b/seed/go-sdk/objects-with-imports/file/types.go new file mode 100644 index 00000000000..bed1f004564 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/file/types.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +import ( + json "encoding/json" + fmt "fmt" + fern "github.com/objects-with-imports/fern" + core "github.com/objects-with-imports/fern/core" +) + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*fern.File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` + + _rawJSON json.RawMessage +} + +func (d *Directory) UnmarshalJSON(data []byte) error { + type unmarshaler Directory + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Directory(value) + d._rawJSON = json.RawMessage(data) + return nil +} + +func (d *Directory) String() string { + if len(d._rawJSON) > 0 { + if value, err := core.StringifyJSON(d._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} diff --git a/seed/go-sdk/objects-with-imports/go.mod b/seed/go-sdk/objects-with-imports/go.mod new file mode 100644 index 00000000000..561f2925088 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/go.mod @@ -0,0 +1,9 @@ +module github.com/objects-with-imports/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/objects-with-imports/go.sum b/seed/go-sdk/objects-with-imports/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/objects-with-imports/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/objects-with-imports/option/request_option.go b/seed/go-sdk/objects-with-imports/option/request_option.go new file mode 100644 index 00000000000..1bb2de7c086 --- /dev/null +++ b/seed/go-sdk/objects-with-imports/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/objects-with-imports/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/objects-with-imports/pointer.go b/seed/go-sdk/objects-with-imports/pointer.go new file mode 100644 index 00000000000..02ef3dd782c --- /dev/null +++ b/seed/go-sdk/objects-with-imports/pointer.go @@ -0,0 +1,132 @@ +package objectswithimports + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/objects-with-imports/snippet.json b/seed/go-sdk/objects-with-imports/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/objects-with-imports/types.go b/seed/go-sdk/objects-with-imports/types.go new file mode 100644 index 00000000000..f6671b999ca --- /dev/null +++ b/seed/go-sdk/objects-with-imports/types.go @@ -0,0 +1,125 @@ +// This file was auto-generated by Fern from our API Definition. + +package objectswithimports + +import ( + json "encoding/json" + fmt "fmt" + commons "github.com/objects-with-imports/fern/commons" + core "github.com/objects-with-imports/fern/core" +) + +type Node struct { + Id string `json:"id" url:"id"` + Label *string `json:"label,omitempty" url:"label,omitempty"` + Metadata *commons.Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + _rawJSON json.RawMessage +} + +func (n *Node) UnmarshalJSON(data []byte) error { + type unmarshaler Node + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *n = Node(value) + n._rawJSON = json.RawMessage(data) + return nil +} + +func (n *Node) String() string { + if len(n._rawJSON) > 0 { + if value, err := core.StringifyJSON(n._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + + _rawJSON json.RawMessage +} + +func (t *Tree) UnmarshalJSON(data []byte) error { + type unmarshaler Tree + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *t = Tree(value) + t._rawJSON = json.RawMessage(data) + return nil +} + +func (t *Tree) String() string { + if len(t._rawJSON) > 0 { + if value, err := core.StringifyJSON(t._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` + Info FileInfo `json:"info,omitempty" url:"info,omitempty"` + + _rawJSON json.RawMessage +} + +func (f *File) UnmarshalJSON(data []byte) error { + type unmarshaler File + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = File(value) + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *File) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type FileInfo string + +const ( + // A regular file (e.g. foo.txt). + FileInfoRegular FileInfo = "REGULAR" + // A directory (e.g. foo/). + FileInfoDirectory FileInfo = "DIRECTORY" +) + +func NewFileInfoFromString(s string) (FileInfo, error) { + switch s { + case "REGULAR": + return FileInfoRegular, nil + case "DIRECTORY": + return FileInfoDirectory, nil + } + var t FileInfo + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (f FileInfo) Ptr() *FileInfo { + return &f +} diff --git a/seed/go-sdk/optional/.github/workflows/ci.yml b/seed/go-sdk/optional/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/optional/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/optional/client/client.go b/seed/go-sdk/optional/client/client.go new file mode 100644 index 00000000000..d4d435ba6d0 --- /dev/null +++ b/seed/go-sdk/optional/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/optional/fern/core" + option "github.com/optional/fern/option" + optional "github.com/optional/fern/optional" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Optional *optional.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Optional: optional.NewClient(opts...), + } +} diff --git a/seed/go-sdk/optional/client/client_test.go b/seed/go-sdk/optional/client/client_test.go new file mode 100644 index 00000000000..49e19e7fc2d --- /dev/null +++ b/seed/go-sdk/optional/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/optional/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/optional/core/core.go b/seed/go-sdk/optional/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/optional/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/optional/core/core_test.go b/seed/go-sdk/optional/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/optional/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/optional/core/query.go b/seed/go-sdk/optional/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/optional/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/optional/core/query_test.go b/seed/go-sdk/optional/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/optional/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/optional/core/request_option.go b/seed/go-sdk/optional/core/request_option.go new file mode 100644 index 00000000000..2991f0a913b --- /dev/null +++ b/seed/go-sdk/optional/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/optional/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/optional/core/retrier.go b/seed/go-sdk/optional/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/optional/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/optional/core/stringer.go b/seed/go-sdk/optional/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/optional/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/optional/core/time.go b/seed/go-sdk/optional/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/optional/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/optional/go.mod b/seed/go-sdk/optional/go.mod new file mode 100644 index 00000000000..2b1dd08ebe0 --- /dev/null +++ b/seed/go-sdk/optional/go.mod @@ -0,0 +1,9 @@ +module github.com/optional/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/optional/go.sum b/seed/go-sdk/optional/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/optional/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/optional/option/request_option.go b/seed/go-sdk/optional/option/request_option.go new file mode 100644 index 00000000000..e7191a54d25 --- /dev/null +++ b/seed/go-sdk/optional/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/optional/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/optional/optional/client.go b/seed/go-sdk/optional/optional/client.go new file mode 100644 index 00000000000..f38e76b2de8 --- /dev/null +++ b/seed/go-sdk/optional/optional/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package optional + +import ( + context "context" + core "github.com/optional/fern/core" + option "github.com/optional/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) SendOptionalBody( + ctx context.Context, + request map[string]interface{}, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "send-optional-body" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/optional/pointer.go b/seed/go-sdk/optional/pointer.go new file mode 100644 index 00000000000..02ef3dd782c --- /dev/null +++ b/seed/go-sdk/optional/pointer.go @@ -0,0 +1,132 @@ +package objectswithimports + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/optional/snippet.json b/seed/go-sdk/optional/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/package-yml/.github/workflows/ci.yml b/seed/go-sdk/package-yml/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/package-yml/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/package-yml/client/client.go b/seed/go-sdk/package-yml/client/client.go new file mode 100644 index 00000000000..62e8f3817b4 --- /dev/null +++ b/seed/go-sdk/package-yml/client/client.go @@ -0,0 +1,72 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + fmt "fmt" + core "github.com/package-yml/fern/core" + option "github.com/package-yml/fern/option" + service "github.com/package-yml/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} + +func (c *Client) Echo( + ctx context.Context, + id string, + request string, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"%v/", id) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/package-yml/client/client_test.go b/seed/go-sdk/package-yml/client/client_test.go new file mode 100644 index 00000000000..57d41b870c9 --- /dev/null +++ b/seed/go-sdk/package-yml/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/package-yml/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/package-yml/core/core.go b/seed/go-sdk/package-yml/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/package-yml/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/package-yml/core/core_test.go b/seed/go-sdk/package-yml/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/package-yml/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/package-yml/core/query.go b/seed/go-sdk/package-yml/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/package-yml/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/package-yml/core/query_test.go b/seed/go-sdk/package-yml/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/package-yml/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/package-yml/core/request_option.go b/seed/go-sdk/package-yml/core/request_option.go new file mode 100644 index 00000000000..da285077f09 --- /dev/null +++ b/seed/go-sdk/package-yml/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/package-yml/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/package-yml/core/retrier.go b/seed/go-sdk/package-yml/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/package-yml/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/package-yml/core/stringer.go b/seed/go-sdk/package-yml/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/package-yml/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/package-yml/core/time.go b/seed/go-sdk/package-yml/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/package-yml/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/package-yml/go.mod b/seed/go-sdk/package-yml/go.mod new file mode 100644 index 00000000000..92626766180 --- /dev/null +++ b/seed/go-sdk/package-yml/go.mod @@ -0,0 +1,9 @@ +module github.com/package-yml/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/package-yml/go.sum b/seed/go-sdk/package-yml/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/package-yml/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/package-yml/option/request_option.go b/seed/go-sdk/package-yml/option/request_option.go new file mode 100644 index 00000000000..88ded483175 --- /dev/null +++ b/seed/go-sdk/package-yml/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/package-yml/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/package-yml/pointer.go b/seed/go-sdk/package-yml/pointer.go new file mode 100644 index 00000000000..c1d50998d77 --- /dev/null +++ b/seed/go-sdk/package-yml/pointer.go @@ -0,0 +1,132 @@ +package packageyml + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/package-yml/service/client.go b/seed/go-sdk/package-yml/service/client.go new file mode 100644 index 00000000000..b975acd19dd --- /dev/null +++ b/seed/go-sdk/package-yml/service/client.go @@ -0,0 +1,65 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + core "github.com/package-yml/fern/core" + option "github.com/package-yml/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Nop( + ctx context.Context, + id string, + nestedId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"%v//%v", id, nestedId) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/package-yml/snippet.json b/seed/go-sdk/package-yml/snippet.json new file mode 100644 index 00000000000..afd3f3523d7 --- /dev/null +++ b/seed/go-sdk/package-yml/snippet.json @@ -0,0 +1,24 @@ +{ + "endpoints": [ + { + "id": { + "path": "/{id}", + "method": "POST" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/package-yml/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.Echo(\n\tcontext.TODO(),\n\t\"id-ksfd9c1\",\n\t\"Hello world!\",\n)\n" + } + }, + { + "id": { + "path": "/{id}/{nestedId}", + "method": "GET" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/package-yml/fern/client\"\n)\n\nclient := fernclient.NewClient()\nerr := client.Service.Nop(\n\tcontext.TODO(),\n\t\"id-a2ijs82\",\n\t\"id-219xca8\",\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/plain-text/.github/workflows/ci.yml b/seed/go-sdk/plain-text/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/plain-text/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/plain-text/client/client.go b/seed/go-sdk/plain-text/client/client.go similarity index 100% rename from generators/go/seed/sdk/plain-text/client/client.go rename to seed/go-sdk/plain-text/client/client.go diff --git a/generators/go/seed/sdk/plain-text/client/client_test.go b/seed/go-sdk/plain-text/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/plain-text/client/client_test.go rename to seed/go-sdk/plain-text/client/client_test.go diff --git a/seed/go-sdk/plain-text/core/core.go b/seed/go-sdk/plain-text/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/plain-text/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/plain-text/core/core_test.go b/seed/go-sdk/plain-text/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/plain-text/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/plain-text/core/query.go b/seed/go-sdk/plain-text/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/plain-text/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/plain-text/core/query_test.go b/seed/go-sdk/plain-text/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/plain-text/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/plain-text/core/request_option.go b/seed/go-sdk/plain-text/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/plain-text/core/request_option.go rename to seed/go-sdk/plain-text/core/request_option.go diff --git a/seed/go-sdk/plain-text/core/retrier.go b/seed/go-sdk/plain-text/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/plain-text/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/plain-text/core/stringer.go b/seed/go-sdk/plain-text/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/plain-text/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/plain-text/core/time.go b/seed/go-sdk/plain-text/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/plain-text/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/plain-text/go.mod b/seed/go-sdk/plain-text/go.mod similarity index 100% rename from generators/go/seed/sdk/plain-text/go.mod rename to seed/go-sdk/plain-text/go.mod diff --git a/seed/go-sdk/plain-text/go.sum b/seed/go-sdk/plain-text/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/plain-text/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/plain-text/option/request_option.go b/seed/go-sdk/plain-text/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/plain-text/option/request_option.go rename to seed/go-sdk/plain-text/option/request_option.go diff --git a/generators/go/seed/sdk/plain-text/pointer.go b/seed/go-sdk/plain-text/pointer.go similarity index 75% rename from generators/go/seed/sdk/plain-text/pointer.go rename to seed/go-sdk/plain-text/pointer.go index ebee0d0b9b8..314e552e651 100644 --- a/generators/go/seed/sdk/plain-text/pointer.go +++ b/seed/go-sdk/plain-text/pointer.go @@ -1,6 +1,10 @@ package plaintext -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/seed/sdk/plain-text/service/client.go b/seed/go-sdk/plain-text/service/client.go similarity index 100% rename from generators/go/seed/sdk/plain-text/service/client.go rename to seed/go-sdk/plain-text/service/client.go diff --git a/seed/go-sdk/plain-text/snippet.json b/seed/go-sdk/plain-text/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/query-parameters/.github/workflows/ci.yml b/seed/go-sdk/query-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/query-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/query-parameters/client/client.go b/seed/go-sdk/query-parameters/client/client.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/client/client.go rename to seed/go-sdk/query-parameters/client/client.go diff --git a/generators/go/seed/sdk/query-parameters/client/client_test.go b/seed/go-sdk/query-parameters/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/client/client_test.go rename to seed/go-sdk/query-parameters/client/client_test.go diff --git a/seed/go-sdk/query-parameters/core/core.go b/seed/go-sdk/query-parameters/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/query-parameters/core/core_test.go b/seed/go-sdk/query-parameters/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/query-parameters/core/query.go b/seed/go-sdk/query-parameters/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/query-parameters/core/query_test.go b/seed/go-sdk/query-parameters/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/query-parameters/core/request_option.go b/seed/go-sdk/query-parameters/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/core/request_option.go rename to seed/go-sdk/query-parameters/core/request_option.go diff --git a/seed/go-sdk/query-parameters/core/retrier.go b/seed/go-sdk/query-parameters/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/query-parameters/core/stringer.go b/seed/go-sdk/query-parameters/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/query-parameters/core/time.go b/seed/go-sdk/query-parameters/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/query-parameters/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/query-parameters/go.mod b/seed/go-sdk/query-parameters/go.mod similarity index 100% rename from generators/go/seed/sdk/query-parameters/go.mod rename to seed/go-sdk/query-parameters/go.mod diff --git a/seed/go-sdk/query-parameters/go.sum b/seed/go-sdk/query-parameters/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/query-parameters/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/query-parameters/option/request_option.go b/seed/go-sdk/query-parameters/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/option/request_option.go rename to seed/go-sdk/query-parameters/option/request_option.go diff --git a/generators/go/seed/sdk/query-parameters/pointer.go b/seed/go-sdk/query-parameters/pointer.go similarity index 75% rename from generators/go/seed/sdk/query-parameters/pointer.go rename to seed/go-sdk/query-parameters/pointer.go index 871221208c3..587a27bd46e 100644 --- a/generators/go/seed/sdk/query-parameters/pointer.go +++ b/seed/go-sdk/query-parameters/pointer.go @@ -1,6 +1,10 @@ package queryparameters -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/query-parameters/snippet.json b/seed/go-sdk/query-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/query-parameters/user.go b/seed/go-sdk/query-parameters/user.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/user.go rename to seed/go-sdk/query-parameters/user.go diff --git a/generators/go/seed/sdk/query-parameters/user/client.go b/seed/go-sdk/query-parameters/user/client.go similarity index 100% rename from generators/go/seed/sdk/query-parameters/user/client.go rename to seed/go-sdk/query-parameters/user/client.go diff --git a/seed/go-sdk/response-property/.github/workflows/ci.yml b/seed/go-sdk/response-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/response-property/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/response-property/client/client.go b/seed/go-sdk/response-property/client/client.go similarity index 100% rename from generators/go/seed/sdk/response-property/client/client.go rename to seed/go-sdk/response-property/client/client.go diff --git a/generators/go/seed/sdk/response-property/client/client_test.go b/seed/go-sdk/response-property/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/response-property/client/client_test.go rename to seed/go-sdk/response-property/client/client_test.go diff --git a/seed/go-sdk/response-property/core/core.go b/seed/go-sdk/response-property/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/response-property/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/response-property/core/core_test.go b/seed/go-sdk/response-property/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/response-property/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/response-property/core/query.go b/seed/go-sdk/response-property/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/response-property/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/response-property/core/query_test.go b/seed/go-sdk/response-property/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/response-property/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/response-property/core/request_option.go b/seed/go-sdk/response-property/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/response-property/core/request_option.go rename to seed/go-sdk/response-property/core/request_option.go diff --git a/seed/go-sdk/response-property/core/retrier.go b/seed/go-sdk/response-property/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/response-property/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/response-property/core/stringer.go b/seed/go-sdk/response-property/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/response-property/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/response-property/core/time.go b/seed/go-sdk/response-property/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/response-property/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/response-property/go.mod b/seed/go-sdk/response-property/go.mod similarity index 100% rename from generators/go/seed/sdk/response-property/go.mod rename to seed/go-sdk/response-property/go.mod diff --git a/seed/go-sdk/response-property/go.sum b/seed/go-sdk/response-property/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/response-property/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/response-property/option/request_option.go b/seed/go-sdk/response-property/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/response-property/option/request_option.go rename to seed/go-sdk/response-property/option/request_option.go diff --git a/generators/go/seed/sdk/response-property/pointer.go b/seed/go-sdk/response-property/pointer.go similarity index 75% rename from generators/go/seed/sdk/response-property/pointer.go rename to seed/go-sdk/response-property/pointer.go index 6bb38e25f9a..0c0a9173d9f 100644 --- a/generators/go/seed/sdk/response-property/pointer.go +++ b/seed/go-sdk/response-property/pointer.go @@ -1,6 +1,10 @@ package responseproperty -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/generators/go/seed/sdk/response-property/service.go b/seed/go-sdk/response-property/service.go similarity index 100% rename from generators/go/seed/sdk/response-property/service.go rename to seed/go-sdk/response-property/service.go diff --git a/generators/go/seed/sdk/response-property/service/client.go b/seed/go-sdk/response-property/service/client.go similarity index 100% rename from generators/go/seed/sdk/response-property/service/client.go rename to seed/go-sdk/response-property/service/client.go diff --git a/seed/go-sdk/response-property/snippet.json b/seed/go-sdk/response-property/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/generators/go/seed/sdk/response-property/types.go b/seed/go-sdk/response-property/types.go similarity index 100% rename from generators/go/seed/sdk/response-property/types.go rename to seed/go-sdk/response-property/types.go diff --git a/generators/go/seed/sdk/seed.yml b/seed/go-sdk/seed.yml similarity index 75% rename from generators/go/seed/sdk/seed.yml rename to seed/go-sdk/seed.yml index 5f56811b86e..9dd4972495a 100644 --- a/generators/go/seed/sdk/seed.yml +++ b/seed/go-sdk/seed.yml @@ -1,6 +1,6 @@ irVersion: v33 docker: fernapi/fern-go-sdk:latest -dockerCommand: docker build -f ./docker/Dockerfile.sdk -t fernapi/fern-go-sdk:latest . +dockerCommand: docker build -f ./generators/go/docker/Dockerfile.sdk -t fernapi/fern-go-sdk:latest ./generators/go language: go generatorType: sdk defaultOutputMode: github @@ -24,3 +24,8 @@ scripts: - docker: golang:1.18-alpine commands: - CGO_ENABLED=0 go test ./... +allowedFailures: + - exhaustive + - reserved-keywords + - trace + - websocket diff --git a/seed/go-sdk/single-url-environment-default/.github/workflows/ci.yml b/seed/go-sdk/single-url-environment-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/single-url-environment-default/client/client.go b/seed/go-sdk/single-url-environment-default/client/client.go new file mode 100644 index 00000000000..3ee19b6d20c --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/single-url-environment-default/fern/core" + dummy "github.com/single-url-environment-default/fern/dummy" + option "github.com/single-url-environment-default/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Dummy *dummy.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Dummy: dummy.NewClient(opts...), + } +} diff --git a/seed/go-sdk/single-url-environment-default/client/client_test.go b/seed/go-sdk/single-url-environment-default/client/client_test.go new file mode 100644 index 00000000000..04a952d57ed --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/single-url-environment-default/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/single-url-environment-default/core/core.go b/seed/go-sdk/single-url-environment-default/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/single-url-environment-default/core/core_test.go b/seed/go-sdk/single-url-environment-default/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/single-url-environment-default/core/query.go b/seed/go-sdk/single-url-environment-default/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/single-url-environment-default/core/query_test.go b/seed/go-sdk/single-url-environment-default/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/single-url-environment-default/core/request_option.go b/seed/go-sdk/single-url-environment-default/core/request_option.go new file mode 100644 index 00000000000..adf1b18db98 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/single-url-environment-default/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/single-url-environment-default/core/retrier.go b/seed/go-sdk/single-url-environment-default/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/single-url-environment-default/core/stringer.go b/seed/go-sdk/single-url-environment-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/single-url-environment-default/core/time.go b/seed/go-sdk/single-url-environment-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/single-url-environment-default/dummy/client.go b/seed/go-sdk/single-url-environment-default/dummy/client.go new file mode 100644 index 00000000000..e9e413fdbda --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/dummy/client.go @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +package dummy + +import ( + context "context" + core "github.com/single-url-environment-default/fern/core" + option "github.com/single-url-environment-default/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetDummy( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "https://production.com/api" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "dummy" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/single-url-environment-default/environments.go b/seed/go-sdk/single-url-environment-default/environments.go new file mode 100644 index 00000000000..c66c18df2a2 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/environments.go @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +package singleurlenvironmentdefault + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Production string + Staging string +}{ + Production: "https://production.com/api", + Staging: "https://staging.com/api", +} diff --git a/seed/go-sdk/single-url-environment-default/go.mod b/seed/go-sdk/single-url-environment-default/go.mod new file mode 100644 index 00000000000..37b921f5f33 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/go.mod @@ -0,0 +1,9 @@ +module github.com/single-url-environment-default/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/single-url-environment-default/go.sum b/seed/go-sdk/single-url-environment-default/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/single-url-environment-default/option/request_option.go b/seed/go-sdk/single-url-environment-default/option/request_option.go new file mode 100644 index 00000000000..f4622a8f49e --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/single-url-environment-default/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/single-url-environment-default/pointer.go b/seed/go-sdk/single-url-environment-default/pointer.go new file mode 100644 index 00000000000..7c600c4ede9 --- /dev/null +++ b/seed/go-sdk/single-url-environment-default/pointer.go @@ -0,0 +1,132 @@ +package singleurlenvironmentdefault + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/single-url-environment-default/snippet.json b/seed/go-sdk/single-url-environment-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/single-url-environment-no-default/.github/workflows/ci.yml b/seed/go-sdk/single-url-environment-no-default/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/single-url-environment-no-default/client/client.go b/seed/go-sdk/single-url-environment-no-default/client/client.go new file mode 100644 index 00000000000..051bcd139d9 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/single-url-environment-no-default/fern/core" + dummy "github.com/single-url-environment-no-default/fern/dummy" + option "github.com/single-url-environment-no-default/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Dummy *dummy.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Dummy: dummy.NewClient(opts...), + } +} diff --git a/seed/go-sdk/single-url-environment-no-default/client/client_test.go b/seed/go-sdk/single-url-environment-no-default/client/client_test.go new file mode 100644 index 00000000000..c5b23ae6663 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/single-url-environment-no-default/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/core.go b/seed/go-sdk/single-url-environment-no-default/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/core_test.go b/seed/go-sdk/single-url-environment-no-default/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/query.go b/seed/go-sdk/single-url-environment-no-default/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/query_test.go b/seed/go-sdk/single-url-environment-no-default/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/request_option.go b/seed/go-sdk/single-url-environment-no-default/core/request_option.go new file mode 100644 index 00000000000..ebc67de2e16 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/single-url-environment-no-default/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/retrier.go b/seed/go-sdk/single-url-environment-no-default/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/stringer.go b/seed/go-sdk/single-url-environment-no-default/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/single-url-environment-no-default/core/time.go b/seed/go-sdk/single-url-environment-no-default/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/single-url-environment-no-default/dummy/client.go b/seed/go-sdk/single-url-environment-no-default/dummy/client.go new file mode 100644 index 00000000000..dce8367fb51 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/dummy/client.go @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +package dummy + +import ( + context "context" + core "github.com/single-url-environment-no-default/fern/core" + option "github.com/single-url-environment-no-default/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetDummy( + ctx context.Context, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/" + "dummy" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/single-url-environment-no-default/environments.go b/seed/go-sdk/single-url-environment-no-default/environments.go new file mode 100644 index 00000000000..bd86e64153a --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/environments.go @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +package singleurlenvironmentnodefault + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Production string + Staging string +}{ + Production: "https://production.com/api", + Staging: "https://staging.com/api", +} diff --git a/seed/go-sdk/single-url-environment-no-default/go.mod b/seed/go-sdk/single-url-environment-no-default/go.mod new file mode 100644 index 00000000000..f6b3dc76ed2 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/go.mod @@ -0,0 +1,9 @@ +module github.com/single-url-environment-no-default/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/single-url-environment-no-default/go.sum b/seed/go-sdk/single-url-environment-no-default/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/single-url-environment-no-default/option/request_option.go b/seed/go-sdk/single-url-environment-no-default/option/request_option.go new file mode 100644 index 00000000000..5b6dbfc3bb8 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/single-url-environment-no-default/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/single-url-environment-no-default/pointer.go b/seed/go-sdk/single-url-environment-no-default/pointer.go new file mode 100644 index 00000000000..67c170d18d9 --- /dev/null +++ b/seed/go-sdk/single-url-environment-no-default/pointer.go @@ -0,0 +1,132 @@ +package singleurlenvironmentnodefault + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/single-url-environment-no-default/snippet.json b/seed/go-sdk/single-url-environment-no-default/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/streaming/.github/workflows/ci.yml b/seed/go-sdk/streaming/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/streaming/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/streaming/client/client.go b/seed/go-sdk/streaming/client/client.go similarity index 100% rename from generators/go/seed/sdk/streaming/client/client.go rename to seed/go-sdk/streaming/client/client.go diff --git a/generators/go/seed/sdk/streaming/client/client_test.go b/seed/go-sdk/streaming/client/client_test.go similarity index 100% rename from generators/go/seed/sdk/streaming/client/client_test.go rename to seed/go-sdk/streaming/client/client_test.go diff --git a/seed/go-sdk/streaming/core/core.go b/seed/go-sdk/streaming/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/streaming/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/streaming/core/core_test.go b/seed/go-sdk/streaming/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/streaming/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/streaming/core/query.go b/seed/go-sdk/streaming/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/streaming/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/streaming/core/query_test.go b/seed/go-sdk/streaming/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/streaming/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/generators/go/seed/sdk/streaming/core/request_option.go b/seed/go-sdk/streaming/core/request_option.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/request_option.go rename to seed/go-sdk/streaming/core/request_option.go diff --git a/seed/go-sdk/streaming/core/retrier.go b/seed/go-sdk/streaming/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/streaming/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/generators/go/seed/sdk/streaming/core/stream.go b/seed/go-sdk/streaming/core/stream.go similarity index 100% rename from generators/go/seed/sdk/streaming/core/stream.go rename to seed/go-sdk/streaming/core/stream.go diff --git a/seed/go-sdk/streaming/core/stringer.go b/seed/go-sdk/streaming/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/streaming/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/streaming/core/time.go b/seed/go-sdk/streaming/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/streaming/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/generators/go/seed/sdk/streaming/dummy.go b/seed/go-sdk/streaming/dummy.go similarity index 100% rename from generators/go/seed/sdk/streaming/dummy.go rename to seed/go-sdk/streaming/dummy.go diff --git a/generators/go/seed/sdk/streaming/dummy/client.go b/seed/go-sdk/streaming/dummy/client.go similarity index 100% rename from generators/go/seed/sdk/streaming/dummy/client.go rename to seed/go-sdk/streaming/dummy/client.go diff --git a/generators/go/seed/sdk/streaming/go.mod b/seed/go-sdk/streaming/go.mod similarity index 100% rename from generators/go/seed/sdk/streaming/go.mod rename to seed/go-sdk/streaming/go.mod diff --git a/seed/go-sdk/streaming/go.sum b/seed/go-sdk/streaming/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/streaming/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/generators/go/seed/sdk/streaming/option/request_option.go b/seed/go-sdk/streaming/option/request_option.go similarity index 100% rename from generators/go/seed/sdk/streaming/option/request_option.go rename to seed/go-sdk/streaming/option/request_option.go diff --git a/generators/go/seed/sdk/streaming/pointer.go b/seed/go-sdk/streaming/pointer.go similarity index 75% rename from generators/go/seed/sdk/streaming/pointer.go rename to seed/go-sdk/streaming/pointer.go index 0dcfdee824f..50f7a7e1a87 100644 --- a/generators/go/seed/sdk/streaming/pointer.go +++ b/seed/go-sdk/streaming/pointer.go @@ -1,6 +1,10 @@ package stream -import "time" +import ( + "time" + + "github.com/google/uuid" +) // Bool returns a pointer to the given bool value. func Bool(b bool) *bool { @@ -97,7 +101,32 @@ func Uintptr(u uintptr) *uintptr { return &u } +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + // Time returns a pointer to the given time.Time value. func Time(t time.Time) *time.Time { return &t } + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/streaming/snippet.json b/seed/go-sdk/streaming/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/undiscriminated-unions/.github/workflows/ci.yml b/seed/go-sdk/undiscriminated-unions/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/undiscriminated-unions/client/client.go b/seed/go-sdk/undiscriminated-unions/client/client.go new file mode 100644 index 00000000000..0f1f43e9801 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/undiscriminated-unions/fern/core" + option "github.com/undiscriminated-unions/fern/option" + union "github.com/undiscriminated-unions/fern/union" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Union *union.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Union: union.NewClient(opts...), + } +} diff --git a/seed/go-sdk/undiscriminated-unions/client/client_test.go b/seed/go-sdk/undiscriminated-unions/client/client_test.go new file mode 100644 index 00000000000..cb666574652 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + assert "github.com/stretchr/testify/assert" + option "github.com/undiscriminated-unions/fern/option" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/undiscriminated-unions/core/core.go b/seed/go-sdk/undiscriminated-unions/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/undiscriminated-unions/core/core_test.go b/seed/go-sdk/undiscriminated-unions/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/undiscriminated-unions/core/query.go b/seed/go-sdk/undiscriminated-unions/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/undiscriminated-unions/core/query_test.go b/seed/go-sdk/undiscriminated-unions/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/undiscriminated-unions/core/request_option.go b/seed/go-sdk/undiscriminated-unions/core/request_option.go new file mode 100644 index 00000000000..ee7ed7df5f9 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/undiscriminated-unions/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/undiscriminated-unions/core/retrier.go b/seed/go-sdk/undiscriminated-unions/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/undiscriminated-unions/core/stringer.go b/seed/go-sdk/undiscriminated-unions/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/undiscriminated-unions/core/time.go b/seed/go-sdk/undiscriminated-unions/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/undiscriminated-unions/go.mod b/seed/go-sdk/undiscriminated-unions/go.mod new file mode 100644 index 00000000000..4153475b74c --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/go.mod @@ -0,0 +1,9 @@ +module github.com/undiscriminated-unions/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/undiscriminated-unions/go.sum b/seed/go-sdk/undiscriminated-unions/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/undiscriminated-unions/option/request_option.go b/seed/go-sdk/undiscriminated-unions/option/request_option.go new file mode 100644 index 00000000000..b8f8b32bb02 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/undiscriminated-unions/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/undiscriminated-unions/pointer.go b/seed/go-sdk/undiscriminated-unions/pointer.go new file mode 100644 index 00000000000..19f0c9a0771 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/pointer.go @@ -0,0 +1,132 @@ +package undiscriminatedunions + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/undiscriminated-unions/snippet.json b/seed/go-sdk/undiscriminated-unions/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/undiscriminated-unions/union.go b/seed/go-sdk/undiscriminated-unions/union.go new file mode 100644 index 00000000000..126f606cab3 --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/union.go @@ -0,0 +1,114 @@ +// This file was auto-generated by Fern from our API Definition. + +package undiscriminatedunions + +import ( + json "encoding/json" + fmt "fmt" +) + +// Several different types are accepted. +type MyUnion struct { + typeName string + String string + StringList []string + Integer int + IntegerList []int + IntegerListList [][]int +} + +func NewMyUnionFromString(value string) *MyUnion { + return &MyUnion{typeName: "string", String: value} +} + +func NewMyUnionFromStringList(value []string) *MyUnion { + return &MyUnion{typeName: "stringList", StringList: value} +} + +func NewMyUnionFromInteger(value int) *MyUnion { + return &MyUnion{typeName: "integer", Integer: value} +} + +func NewMyUnionFromIntegerList(value []int) *MyUnion { + return &MyUnion{typeName: "integerList", IntegerList: value} +} + +func NewMyUnionFromIntegerListList(value [][]int) *MyUnion { + return &MyUnion{typeName: "integerListList", IntegerListList: value} +} + +func (m *MyUnion) UnmarshalJSON(data []byte) error { + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.typeName = "string" + m.String = valueString + return nil + } + var valueStringList []string + if err := json.Unmarshal(data, &valueStringList); err == nil { + m.typeName = "stringList" + m.StringList = valueStringList + return nil + } + var valueInteger int + if err := json.Unmarshal(data, &valueInteger); err == nil { + m.typeName = "integer" + m.Integer = valueInteger + return nil + } + var valueIntegerList []int + if err := json.Unmarshal(data, &valueIntegerList); err == nil { + m.typeName = "integerList" + m.IntegerList = valueIntegerList + return nil + } + var valueIntegerListList [][]int + if err := json.Unmarshal(data, &valueIntegerListList); err == nil { + m.typeName = "integerListList" + m.IntegerListList = valueIntegerListList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MyUnion) MarshalJSON() ([]byte, error) { + switch m.typeName { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return json.Marshal(m.String) + case "stringList": + return json.Marshal(m.StringList) + case "integer": + return json.Marshal(m.Integer) + case "integerList": + return json.Marshal(m.IntegerList) + case "integerListList": + return json.Marshal(m.IntegerListList) + } +} + +type MyUnionVisitor interface { + VisitString(string) error + VisitStringList([]string) error + VisitInteger(int) error + VisitIntegerList([]int) error + VisitIntegerListList([][]int) error +} + +func (m *MyUnion) Accept(visitor MyUnionVisitor) error { + switch m.typeName { + default: + return fmt.Errorf("invalid type %s in %T", m.typeName, m) + case "string": + return visitor.VisitString(m.String) + case "stringList": + return visitor.VisitStringList(m.StringList) + case "integer": + return visitor.VisitInteger(m.Integer) + case "integerList": + return visitor.VisitIntegerList(m.IntegerList) + case "integerListList": + return visitor.VisitIntegerListList(m.IntegerListList) + } +} diff --git a/seed/go-sdk/undiscriminated-unions/union/client.go b/seed/go-sdk/undiscriminated-unions/union/client.go new file mode 100644 index 00000000000..1c3137df35b --- /dev/null +++ b/seed/go-sdk/undiscriminated-unions/union/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package union + +import ( + context "context" + fern "github.com/undiscriminated-unions/fern" + core "github.com/undiscriminated-unions/fern/core" + option "github.com/undiscriminated-unions/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Get( + ctx context.Context, + request *fern.MyUnion, + opts ...option.RequestOption, +) (*fern.MyUnion, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.MyUnion + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/unknown/.github/workflows/ci.yml b/seed/go-sdk/unknown/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/unknown/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/generators/go/seed/sdk/literal/client/client.go b/seed/go-sdk/unknown/client/client.go similarity index 73% rename from generators/go/seed/sdk/literal/client/client.go rename to seed/go-sdk/unknown/client/client.go index d8b7afa0432..5b75d53d79a 100644 --- a/generators/go/seed/sdk/literal/client/client.go +++ b/seed/go-sdk/unknown/client/client.go @@ -3,9 +3,9 @@ package client import ( - core "github.com/literal/fern/core" - literal "github.com/literal/fern/literal" - option "github.com/literal/fern/option" + core "github.com/unknown/fern/core" + option "github.com/unknown/fern/option" + unknown "github.com/unknown/fern/unknown" http "net/http" ) @@ -14,7 +14,7 @@ type Client struct { caller *core.Caller header http.Header - Literal *literal.Client + Unknown *unknown.Client } func NewClient(opts ...option.RequestOption) *Client { @@ -28,6 +28,6 @@ func NewClient(opts ...option.RequestOption) *Client { }, ), header: options.ToHeader(), - Literal: literal.NewClient(opts...), + Unknown: unknown.NewClient(opts...), } } diff --git a/seed/go-sdk/unknown/client/client_test.go b/seed/go-sdk/unknown/client/client_test.go new file mode 100644 index 00000000000..fb2962a5183 --- /dev/null +++ b/seed/go-sdk/unknown/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + assert "github.com/stretchr/testify/assert" + option "github.com/unknown/fern/option" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/unknown/core/core.go b/seed/go-sdk/unknown/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/unknown/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/unknown/core/core_test.go b/seed/go-sdk/unknown/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/unknown/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/unknown/core/query.go b/seed/go-sdk/unknown/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/unknown/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/unknown/core/query_test.go b/seed/go-sdk/unknown/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/unknown/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/unknown/core/request_option.go b/seed/go-sdk/unknown/core/request_option.go new file mode 100644 index 00000000000..c46d45a36bc --- /dev/null +++ b/seed/go-sdk/unknown/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/unknown/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/unknown/core/retrier.go b/seed/go-sdk/unknown/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/unknown/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/unknown/core/stringer.go b/seed/go-sdk/unknown/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/unknown/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/unknown/core/time.go b/seed/go-sdk/unknown/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/unknown/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/unknown/go.mod b/seed/go-sdk/unknown/go.mod new file mode 100644 index 00000000000..b59b121e378 --- /dev/null +++ b/seed/go-sdk/unknown/go.mod @@ -0,0 +1,9 @@ +module github.com/unknown/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/unknown/go.sum b/seed/go-sdk/unknown/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/unknown/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/unknown/option/request_option.go b/seed/go-sdk/unknown/option/request_option.go new file mode 100644 index 00000000000..b39c6a7dc43 --- /dev/null +++ b/seed/go-sdk/unknown/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/unknown/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/unknown/pointer.go b/seed/go-sdk/unknown/pointer.go new file mode 100644 index 00000000000..02899c6061a --- /dev/null +++ b/seed/go-sdk/unknown/pointer.go @@ -0,0 +1,132 @@ +package unknownasany + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/unknown/snippet.json b/seed/go-sdk/unknown/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/unknown/types.go b/seed/go-sdk/unknown/types.go new file mode 100644 index 00000000000..c35662b443a --- /dev/null +++ b/seed/go-sdk/unknown/types.go @@ -0,0 +1,40 @@ +// This file was auto-generated by Fern from our API Definition. + +package unknownasany + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/unknown/fern/core" +) + +type MyAlias = interface{} + +type MyObject struct { + Unknown interface{} `json:"unknown,omitempty" url:"unknown,omitempty"` + + _rawJSON json.RawMessage +} + +func (m *MyObject) UnmarshalJSON(data []byte) error { + type unmarshaler MyObject + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = MyObject(value) + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *MyObject) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} diff --git a/seed/go-sdk/unknown/unknown/client.go b/seed/go-sdk/unknown/unknown/client.go new file mode 100644 index 00000000000..8fa63347b1f --- /dev/null +++ b/seed/go-sdk/unknown/unknown/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package unknown + +import ( + context "context" + core "github.com/unknown/fern/core" + option "github.com/unknown/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Post( + ctx context.Context, + request interface{}, + opts ...option.RequestOption, +) ([]interface{}, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response []interface{} + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/variables/.github/workflows/ci.yml b/seed/go-sdk/variables/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/variables/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/variables/client/client.go b/seed/go-sdk/variables/client/client.go new file mode 100644 index 00000000000..15d4114fcfa --- /dev/null +++ b/seed/go-sdk/variables/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/variables/fern/core" + option "github.com/variables/fern/option" + service "github.com/variables/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/variables/client/client_test.go b/seed/go-sdk/variables/client/client_test.go new file mode 100644 index 00000000000..76d5732fc7e --- /dev/null +++ b/seed/go-sdk/variables/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + assert "github.com/stretchr/testify/assert" + option "github.com/variables/fern/option" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/variables/core/core.go b/seed/go-sdk/variables/core/core.go new file mode 100644 index 00000000000..5277d138d27 --- /dev/null +++ b/seed/go-sdk/variables/core/core.go @@ -0,0 +1,269 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if request != nil { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} diff --git a/seed/go-sdk/variables/core/core_test.go b/seed/go-sdk/variables/core/core_test.go new file mode 100644 index 00000000000..f476f9ee383 --- /dev/null +++ b/seed/go-sdk/variables/core/core_test.go @@ -0,0 +1,284 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + + request := new(Request) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/variables/core/query.go b/seed/go-sdk/variables/core/query.go new file mode 100644 index 00000000000..479cbb24d18 --- /dev/null +++ b/seed/go-sdk/variables/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/variables/core/query_test.go b/seed/go-sdk/variables/core/query_test.go new file mode 100644 index 00000000000..4f0d39284f4 --- /dev/null +++ b/seed/go-sdk/variables/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/seed/go-sdk/variables/core/request_option.go b/seed/go-sdk/variables/core/request_option.go new file mode 100644 index 00000000000..6afb0c1b50f --- /dev/null +++ b/seed/go-sdk/variables/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/variables/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/variables/core/retrier.go b/seed/go-sdk/variables/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/variables/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/variables/core/stringer.go b/seed/go-sdk/variables/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/variables/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/variables/core/time.go b/seed/go-sdk/variables/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/variables/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/variables/go.mod b/seed/go-sdk/variables/go.mod new file mode 100644 index 00000000000..0e03ca9ee89 --- /dev/null +++ b/seed/go-sdk/variables/go.mod @@ -0,0 +1,9 @@ +module github.com/variables/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/variables/go.sum b/seed/go-sdk/variables/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/variables/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/variables/option/request_option.go b/seed/go-sdk/variables/option/request_option.go new file mode 100644 index 00000000000..b76e4499674 --- /dev/null +++ b/seed/go-sdk/variables/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/variables/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/variables/pointer.go b/seed/go-sdk/variables/pointer.go new file mode 100644 index 00000000000..04c36b86d66 --- /dev/null +++ b/seed/go-sdk/variables/pointer.go @@ -0,0 +1,132 @@ +package variables + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/variables/service/client.go b/seed/go-sdk/variables/service/client.go new file mode 100644 index 00000000000..af3ffef8feb --- /dev/null +++ b/seed/go-sdk/variables/service/client.go @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + core "github.com/variables/fern/core" + option "github.com/variables/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Post( + ctx context.Context, + endpointParam string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"%v", endpointParam) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} diff --git a/seed/go-sdk/variables/snippet.json b/seed/go-sdk/variables/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/python-sdk/alias/poetry.lock b/seed/python-sdk/alias/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/alias/poetry.lock +++ b/seed/python-sdk/alias/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/api-wide-base-path/poetry.lock b/seed/python-sdk/api-wide-base-path/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/api-wide-base-path/poetry.lock +++ b/seed/python-sdk/api-wide-base-path/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/audiences/poetry.lock b/seed/python-sdk/audiences/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/audiences/poetry.lock +++ b/seed/python-sdk/audiences/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/auth-environment-variables/poetry.lock b/seed/python-sdk/auth-environment-variables/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/auth-environment-variables/poetry.lock +++ b/seed/python-sdk/auth-environment-variables/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/basic-auth/poetry.lock b/seed/python-sdk/basic-auth/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/basic-auth/poetry.lock +++ b/seed/python-sdk/basic-auth/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/bearer-token-environment-variable/poetry.lock b/seed/python-sdk/bearer-token-environment-variable/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/bearer-token-environment-variable/poetry.lock +++ b/seed/python-sdk/bearer-token-environment-variable/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/bytes/poetry.lock b/seed/python-sdk/bytes/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/bytes/poetry.lock +++ b/seed/python-sdk/bytes/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/circular-references/poetry.lock b/seed/python-sdk/circular-references/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/circular-references/poetry.lock +++ b/seed/python-sdk/circular-references/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/custom-auth/poetry.lock b/seed/python-sdk/custom-auth/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/custom-auth/poetry.lock +++ b/seed/python-sdk/custom-auth/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/enum/poetry.lock b/seed/python-sdk/enum/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/enum/poetry.lock +++ b/seed/python-sdk/enum/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/error-property/poetry.lock b/seed/python-sdk/error-property/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/error-property/poetry.lock +++ b/seed/python-sdk/error-property/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/examples/poetry.lock b/seed/python-sdk/examples/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/examples/poetry.lock +++ b/seed/python-sdk/examples/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock b/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock index 7888a4b8943..cb12591a285 100644 --- a/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock +++ b/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock @@ -126,13 +126,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -143,17 +143,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock b/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock +++ b/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock b/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock +++ b/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/no-custom-config/poetry.lock b/seed/python-sdk/exhaustive/no-custom-config/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/poetry.lock +++ b/seed/python-sdk/exhaustive/no-custom-config/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock b/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/exhaustive/union-utils/poetry.lock b/seed/python-sdk/exhaustive/union-utils/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/exhaustive/union-utils/poetry.lock +++ b/seed/python-sdk/exhaustive/union-utils/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/extends/poetry.lock b/seed/python-sdk/extends/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/extends/poetry.lock +++ b/seed/python-sdk/extends/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/file-download/poetry.lock b/seed/python-sdk/file-download/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/file-download/poetry.lock +++ b/seed/python-sdk/file-download/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/file-upload/poetry.lock b/seed/python-sdk/file-upload/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/file-upload/poetry.lock +++ b/seed/python-sdk/file-upload/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/folders/poetry.lock b/seed/python-sdk/folders/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/folders/poetry.lock +++ b/seed/python-sdk/folders/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/idempotency-headers/poetry.lock b/seed/python-sdk/idempotency-headers/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/idempotency-headers/poetry.lock +++ b/seed/python-sdk/idempotency-headers/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/imdb/poetry.lock b/seed/python-sdk/imdb/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/imdb/poetry.lock +++ b/seed/python-sdk/imdb/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/literal/poetry.lock b/seed/python-sdk/literal/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/literal/poetry.lock +++ b/seed/python-sdk/literal/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/multi-url-environment/poetry.lock b/seed/python-sdk/multi-url-environment/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/multi-url-environment/poetry.lock +++ b/seed/python-sdk/multi-url-environment/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/no-environment/poetry.lock b/seed/python-sdk/no-environment/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/no-environment/poetry.lock +++ b/seed/python-sdk/no-environment/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/object/poetry.lock b/seed/python-sdk/object/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/object/poetry.lock +++ b/seed/python-sdk/object/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/objects-with-imports/poetry.lock b/seed/python-sdk/objects-with-imports/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/objects-with-imports/poetry.lock +++ b/seed/python-sdk/objects-with-imports/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/optional/poetry.lock b/seed/python-sdk/optional/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/optional/poetry.lock +++ b/seed/python-sdk/optional/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/package-yml/poetry.lock b/seed/python-sdk/package-yml/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/package-yml/poetry.lock +++ b/seed/python-sdk/package-yml/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/plain-text/poetry.lock b/seed/python-sdk/plain-text/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/plain-text/poetry.lock +++ b/seed/python-sdk/plain-text/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/query-parameters/poetry.lock b/seed/python-sdk/query-parameters/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/query-parameters/poetry.lock +++ b/seed/python-sdk/query-parameters/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/reserved-keywords/poetry.lock b/seed/python-sdk/reserved-keywords/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/reserved-keywords/poetry.lock +++ b/seed/python-sdk/reserved-keywords/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/single-url-environment-default/poetry.lock b/seed/python-sdk/single-url-environment-default/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/single-url-environment-default/poetry.lock +++ b/seed/python-sdk/single-url-environment-default/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/single-url-environment-no-default/poetry.lock b/seed/python-sdk/single-url-environment-no-default/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/single-url-environment-no-default/poetry.lock +++ b/seed/python-sdk/single-url-environment-no-default/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/streaming/poetry.lock b/seed/python-sdk/streaming/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/streaming/poetry.lock +++ b/seed/python-sdk/streaming/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/trace/poetry.lock b/seed/python-sdk/trace/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/trace/poetry.lock +++ b/seed/python-sdk/trace/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/undiscriminated-unions/poetry.lock b/seed/python-sdk/undiscriminated-unions/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/undiscriminated-unions/poetry.lock +++ b/seed/python-sdk/undiscriminated-unions/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/unknown/poetry.lock b/seed/python-sdk/unknown/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/unknown/poetry.lock +++ b/seed/python-sdk/unknown/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/python-sdk/variables/poetry.lock b/seed/python-sdk/variables/poetry.lock index 8270a062427..bdeefd84a9b 100644 --- a/seed/python-sdk/variables/poetry.lock +++ b/seed/python-sdk/variables/poetry.lock @@ -85,13 +85,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -102,17 +102,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] diff --git a/seed/ts-sdk/seed.yml b/seed/ts-sdk/seed.yml index a11097fc812..dd90653ee0d 100644 --- a/seed/ts-sdk/seed.yml +++ b/seed/ts-sdk/seed.yml @@ -95,4 +95,4 @@ allowedFailures: - auth-environment-variables - exhaustive:bundle - exhaustive:dev-dependencies - + - audiences