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
13 changes: 5 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ MAIN_PATH=./cmd/oastools
BENCH_DIR=benchmarks
BENCH_TIME=5s

# Pin GOROOT to the SDK matching go.mod when system Go is older.
# Update the version here when bumping go.mod's go directive.
GO_SDK := $(wildcard $(HOME)/sdk/go1.25.8)
QUALITY_TARGETS := tidy fmt test test-quick test-race test-full test-coverage test-corpus test-corpus-short integration-test integration-test-debug count-tests count-benchmarks lint vet
ifneq ($(GO_SDK),)
$(QUALITY_TARGETS): export GOROOT = $(GO_SDK)
$(QUALITY_TARGETS): export PATH := $(GO_SDK)/bin:$(PATH)
endif
# Allow go commands to automatically download the toolchain version declared in
# go.mod when the local Go binary is older. This is the portable alternative to
# pinning GOROOT/PATH to a specific SDK path.
QUALITY_TARGETS := tidy fmt test test-quick test-race test-full test-coverage test-corpus test-corpus-short integration-test integration-test-debug count-tests count-benchmarks lint vet check
$(QUALITY_TARGETS): export GOTOOLCHAIN = auto

# Default target
all: build
Expand Down
37 changes: 9 additions & 28 deletions converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,7 @@ func writeTempYAML(t *testing.T, doc any) string {
// TestConverterNew tests the New() constructor
func TestConverterNew(t *testing.T) {
c := New()

if c == nil {
t.Fatal("Expected non-nil Converter")
}
require.NotNil(t, c, "Expected non-nil Converter")

if c.StrictMode {
t.Error("Expected StrictMode to be false by default")
Expand Down Expand Up @@ -580,14 +577,10 @@ func TestRefRewritingOAS2ToOAS3(t *testing.T) {

// Verify refs were rewritten in components/schemas
ownerSchema := oas3Doc.Components.Schemas["Owner"]
if ownerSchema == nil {
t.Fatal("Owner schema not found")
}
require.NotNil(t, ownerSchema, "Owner schema not found")

petProp := ownerSchema.Properties["pet"]
if petProp == nil {
t.Fatal("Pet property not found")
}
require.NotNil(t, petProp, "Pet property not found")

expectedRef := "#/components/schemas/Pet"
if petProp.Ref != expectedRef {
Expand All @@ -596,14 +589,10 @@ func TestRefRewritingOAS2ToOAS3(t *testing.T) {

// Verify refs were rewritten in paths
pathItem := oas3Doc.Paths["/pets"]
if pathItem == nil {
t.Fatal("Path /pets not found")
}
require.NotNil(t, pathItem, "Path /pets not found")

responseSchema := pathItem.Get.Responses.Codes["200"].Content["application/json"].Schema
if responseSchema == nil {
t.Fatal("Response schema not found")
}
require.NotNil(t, responseSchema, "Response schema not found")

if responseSchema.Ref != expectedRef {
t.Errorf("Expected response schema ref '%s', got '%s'", expectedRef, responseSchema.Ref)
Expand Down Expand Up @@ -689,14 +678,10 @@ func TestRefRewritingOAS3ToOAS2(t *testing.T) {

// Verify refs were rewritten in definitions
ownerSchema := oas2Doc.Definitions["Owner"]
if ownerSchema == nil {
t.Fatal("Owner schema not found")
}
require.NotNil(t, ownerSchema, "Owner schema not found")

petProp := ownerSchema.Properties["pet"]
if petProp == nil {
t.Fatal("Pet property not found")
}
require.NotNil(t, petProp, "Pet property not found")

expectedRef := "#/definitions/Pet"
if petProp.Ref != expectedRef {
Expand All @@ -705,14 +690,10 @@ func TestRefRewritingOAS3ToOAS2(t *testing.T) {

// Verify refs were rewritten in paths
pathItem := oas2Doc.Paths["/pets"]
if pathItem == nil {
t.Fatal("Path /pets not found")
}
require.NotNil(t, pathItem, "Path /pets not found")

responseSchema := pathItem.Get.Responses.Codes["200"].Schema
if responseSchema == nil {
t.Fatal("Response schema not found")
}
require.NotNil(t, responseSchema, "Response schema not found")

if responseSchema.Ref != expectedRef {
t.Errorf("Expected response schema ref '%s', got '%s'", expectedRef, responseSchema.Ref)
Expand Down
31 changes: 28 additions & 3 deletions converter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,35 @@ func (c *Converter) convertOAS2ParameterToOAS3(param *parser.Parameter, result *
if param.Schema != nil {
converted.Schema = c.convertOAS2SchemaToOAS3(param.Schema)
} else if param.Type != "" {
// Convert type/format to schema
// Convert type/format to schema, transferring all OAS 2.0 validation keywords
converted.Schema = &parser.Schema{
Type: param.Type,
Format: param.Format,
Type: param.Type,
Format: param.Format,
Default: param.Default,
Enum: param.Enum,
Maximum: param.Maximum,
Minimum: param.Minimum,
MaxLength: param.MaxLength,
MinLength: param.MinLength,
Pattern: param.Pattern,
MaxItems: param.MaxItems,
MinItems: param.MinItems,
UniqueItems: param.UniqueItems,
MultipleOf: param.MultipleOf,
}
if param.ExclusiveMaximum {
converted.Schema.ExclusiveMaximum = true
}
if param.ExclusiveMinimum {
converted.Schema.ExclusiveMinimum = true
}
Comment thread
erraggy marked this conversation as resolved.
if param.Items != nil {
converted.Schema.Items = convertOAS2ItemsToSchema(param.Items)
if param.Items.CollectionFormat != "" && param.Items.CollectionFormat != "csv" {
c.addIssueWithContext(result, path,
fmt.Sprintf("Parameter items uses collectionFormat '%s'", param.Items.CollectionFormat),
"OAS 3.x uses 'style' and 'explode' instead; 'csv' format maps to style=form")
}
}

// Handle collection format
Expand Down
127 changes: 127 additions & 0 deletions converter/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,133 @@ func assertHasIssueContaining(t *testing.T, issues []ConversionIssue, substring
t.Errorf("Expected at least one issue containing %q, but none found in %d issues", substring, len(issues))
}

// TestConvertOAS2ParameterToOAS3_ArrayItems verifies that Items and validation
// keywords on OAS 2.0 array parameters are transferred to the OAS 3.0 schema
// (issue #357).
func TestConvertOAS2ParameterToOAS3_ArrayItems(t *testing.T) {
c := newConverter()
result := &ConversionResult{}

strType := "string"
param := &parser.Parameter{
Name: "chain_id",
In: "query",
Type: "array",
Items: &parser.Items{Type: strType},
}

converted := c.convertOAS2ParameterToOAS3(param, result, "test")
require.NotNil(t, converted)
require.NotNil(t, converted.Schema)
assert.Equal(t, "array", converted.Schema.Type)
require.NotNil(t, converted.Schema.Items, "Items should be transferred from OAS 2.0 parameter")

itemsSchema, ok := converted.Schema.Items.(*parser.Schema)
require.True(t, ok, "Items should be *Schema")
assert.Equal(t, strType, itemsSchema.Type)
}

func TestConvertOAS2ParameterToOAS3_ValidationKeywords(t *testing.T) {
c := newConverter()
result := &ConversionResult{}

min := float64(1)
max := float64(100)
minLen := 2
maxLen := 50
param := &parser.Parameter{
Name: "q",
In: "query",
Type: "string",
Minimum: &min,
Maximum: &max,
MinLength: &minLen,
MaxLength: &maxLen,
Pattern: "^[a-z]+$",
Enum: []any{"a", "b"},
}

converted := c.convertOAS2ParameterToOAS3(param, result, "test")
require.NotNil(t, converted.Schema)
assert.Equal(t, &min, converted.Schema.Minimum)
assert.Equal(t, &max, converted.Schema.Maximum)
assert.Equal(t, &minLen, converted.Schema.MinLength)
assert.Equal(t, &maxLen, converted.Schema.MaxLength)
assert.Equal(t, "^[a-z]+$", converted.Schema.Pattern)
assert.Equal(t, []any{"a", "b"}, converted.Schema.Enum)
}

// TestConvertOAS2ParameterToOAS3_ExclusiveKeywords verifies that ExclusiveMaximum
// and ExclusiveMinimum bool flags are transferred to the OAS 3.x schema.
func TestConvertOAS2ParameterToOAS3_ExclusiveKeywords(t *testing.T) {
c := newConverter()
result := &ConversionResult{}

min := float64(0)
max := float64(10)
param := &parser.Parameter{
Name: "count",
In: "query",
Type: "integer",
Minimum: &min,
Maximum: &max,
ExclusiveMinimum: true,
ExclusiveMaximum: true,
}

converted := c.convertOAS2ParameterToOAS3(param, result, "test")
require.NotNil(t, converted.Schema)
assert.Equal(t, true, converted.Schema.ExclusiveMinimum, "ExclusiveMinimum should be transferred")
assert.Equal(t, true, converted.Schema.ExclusiveMaximum, "ExclusiveMaximum should be transferred")
}

// TestConvertOAS2ParameterToOAS3_ItemsCollectionFormat verifies that a
// non-csv collectionFormat on items generates a conversion warning.
func TestConvertOAS2ParameterToOAS3_ItemsCollectionFormat(t *testing.T) {
c := newConverter()
result := &ConversionResult{}

param := &parser.Parameter{
Name: "tags",
In: "query",
Type: "array",
Items: &parser.Items{
Type: "string",
CollectionFormat: "pipes",
},
}

c.convertOAS2ParameterToOAS3(param, result, "test")
assert.Greater(t, countIssuesContaining(result.Issues, "collectionFormat"), 0,
"should warn about non-csv collectionFormat on items")
}

// TestConvertOAS2ParameterToOAS3_ArrayWithoutItems verifies that an array
// parameter with nil Items is handled gracefully.
func TestConvertOAS2ParameterToOAS3_ArrayWithoutItems(t *testing.T) {
c := newConverter()
result := &ConversionResult{}

param := &parser.Parameter{
Name: "tags",
In: "query",
Type: "array",
// Items intentionally nil
}

converted := c.convertOAS2ParameterToOAS3(param, result, "test")
require.NotNil(t, converted)
require.NotNil(t, converted.Schema)
assert.Equal(t, "array", converted.Schema.Type, "Schema.Type should be array")
assert.Nil(t, converted.Schema.Items, "Schema.Items should be nil when source has no Items")
}

// newConverter creates a Converter for unit testing helpers using the same
// initialization path as production code.
func newConverter() *Converter {
return New()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// countIssuesContaining counts issues whose message contains the given substring.
func countIssuesContaining(issues []ConversionIssue, substring string) int {
count := 0
Expand Down
2 changes: 2 additions & 0 deletions fixer/fixer_corpus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestCorpus_FixerReducesErrors(t *testing.T) {
spec := corpusutil.GetByName("DigitalOcean")
if spec == nil {
t.Skip("DigitalOcean spec not found in corpus")
return
}
corpusutil.SkipIfNotCached(t, *spec)
corpusutil.SkipLargeInShortMode(t, *spec)
Expand Down Expand Up @@ -161,6 +162,7 @@ func TestCorpus_FixerWithInferTypes(t *testing.T) {
spec := corpusutil.GetByName("Asana")
if spec == nil {
t.Skip("Asana spec not found in corpus")
return
}
corpusutil.SkipIfNotCached(t, *spec)

Expand Down
Loading
Loading