Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions generator/golang/coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,28 @@ func TestSharedNameAndReferenceHelpers(t *testing.T) {
if got := RefName("#/components/schemas/Tenant~1Record~0V2"); got != "Tenant/Record~V2" {
t.Fatalf("decoded reference name = %q", got)
}
if got := NewGenerator(WithFormatMapping("date-time", "time.Time", "time")).ScalarType("date-time", "string"); got != "time.Time" {
t.Fatalf("mapped scalar type = %q", got)
}
if got := NewGenerator().ScalarType("unknown", "string"); got != "string" {
t.Fatalf("fallback scalar type = %q", got)
mapped := NewGenerator(WithFormatMapping("date-time", "time.Time", "time"))
for _, test := range []struct {
jsonType string
format string
want string
scalar bool
}{
{jsonType: "string", format: "date-time", want: "time.Time", scalar: true},
{jsonType: "string", format: "uuid", want: "string", scalar: true},
{jsonType: "integer", format: "int32", want: "int32", scalar: true},
{jsonType: "integer", format: "int64", want: "int64", scalar: true},
{jsonType: "integer", want: "int", scalar: true},
{jsonType: "number", format: "float", want: "float32", scalar: true},
{jsonType: "number", format: "double", want: "float64", scalar: true},
{jsonType: "boolean", want: "bool", scalar: true},
{jsonType: "array"},
{jsonType: "object"},
} {
got, scalar := mapped.ScalarType(test.jsonType, test.format)
if got != test.want || scalar != test.scalar {
t.Fatalf("ScalarType(%q, %q) = %q, %t; want %q, %t", test.jsonType, test.format, got, scalar, test.want, test.scalar)
}
}
}

Expand Down
21 changes: 15 additions & 6 deletions generator/golang/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,22 @@ type GeneratedField struct {
Type string
}

// ScalarType returns the configured Go type for an OpenAPI string format, or
// fallback when the generator has no mapping for format.
func (g *Generator) ScalarType(format, fallback string) string {
if mapping, ok := g.formatMappings[format]; ok {
return mapping.goType
// ScalarType returns the Go type this generator renders for a scalar JSON
// Schema type and format, including configured string format mappings. The
// second result is false when jsonType is not a scalar. SDK emitters call it so
// parameter types and model field types come from one mapping.
func (g *Generator) ScalarType(jsonType, format string) (string, bool) {
kind := kindForJSONType(jsonType)
switch kind {
case KindString:
if mapping, ok := g.formatMappings[format]; ok {
return mapping.goType, true
}
return builtinScalarType(kind, format), true
case KindInteger, KindNumber, KindBoolean:
return builtinScalarType(kind, format), true
}
return fallback
return "", false
}

// NewGenerator creates a Go model generator.
Expand Down
45 changes: 27 additions & 18 deletions generator/golang/to_go.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,24 +418,9 @@ func (g *Generator) goType(ir *SchemaIR, required bool, field bool) string {
case KindMap:
typ = "map[string]" + g.goType(ir.AdditionalProperties, true, false)
case KindString:
typ = g.formatType(ir.Format, "string")
case KindInteger:
switch ir.Format {
case "int32":
typ = "int32"
case "int64":
typ = "int64"
default:
typ = "int"
}
case KindNumber:
if ir.Format == "float" {
typ = "float32"
} else {
typ = "float64"
}
case KindBoolean:
typ = "bool"
typ = g.formatType(ir.Format, builtinScalarType(ir.Kind, ir.Format))
case KindInteger, KindNumber, KindBoolean:
typ = builtinScalarType(ir.Kind, ir.Format)
case KindEnum:
if ir.Name != "" {
typ = ir.Name
Expand All @@ -462,6 +447,30 @@ func (g *Generator) formatType(format, fallback string) string {
return fallback
}

// builtinScalarType is the one mapping from a scalar kind and format to a Go
// builtin. goType and ScalarType both use it, so generated models and SDK
// parameters cannot drift apart.
func builtinScalarType(kind Kind, format string) string {
switch kind {
case KindInteger:
switch format {
case "int32":
return "int32"
case "int64":
return "int64"
}
return "int"
case KindNumber:
if format == "float" {
return "float32"
}
return "float64"
case KindBoolean:
return "bool"
}
return "string"
}

func pointerDepth(typ string, ir *SchemaIR, required, optionalPointers, nullablePointer, optionalNullableDoublePointer bool) int {
compound := typ == "any" || strings.HasPrefix(typ, "[]") || strings.HasPrefix(typ, "map[")
nullable := ir != nil && ir.Nullable && nullablePointer
Expand Down
4 changes: 2 additions & 2 deletions generator/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Arazzo. Language emitters consume that prepared contract. Generated clients do
not import libopenapi or parse specifications at runtime.

The first emitter is `generator/sdk/golang`. It generates:
The first emitter is `generator/sdk/gosdk`. It generates:

- reachable models through `generator/golang`;
- one shared HTTP client and security runtime;
Expand All @@ -27,7 +27,7 @@ if err != nil {
return err
}

result, err := sdkgolang.GenerateContract(contract, sdkgolang.Options{
result, err := gosdk.GenerateContract(contract, gosdk.Options{
PackageName: "exampleapi",
})
```
Expand Down
2 changes: 1 addition & 1 deletion generator/sdk/golang/doc.go → generator/sdk/gosdk/doc.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2026 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT

// Package golang emits an idiomatic, resource-oriented Go SDK from a prepared
// Package gosdk emits an idiomatic, resource-oriented Go SDK from a prepared
// OpenAPI client contract.
package gosdk
Loading
Loading