Skip to content

Commit 4215825

Browse files
feat: add Swagger 2.0 support with upgrade to OpenAPI 3.0 utility (#66)
1 parent ed689a1 commit 4215825

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+13559
-315
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ The `arazzo` package provides an API for working with Arazzo documents including
6969

7070
The `openapi` package provides an API for working with OpenAPI documents including reading, creating, mutating, walking, validating and upgrading them. Supports both OpenAPI 3.0.x and 3.1.x specifications.
7171

72+
### [swagger](./swagger)
73+
74+
The `swagger` package provides an API for working with Swagger 2.0 documents including reading, creating, mutating, walking, validating, and upgrading them to OpenAPI 3.0.
75+
7276
### [overlay](./overlay)
7377

7478
The `overlay` package provides an API for working with OpenAPI Overlays including applying overlays to specifications, comparing specifications to generate overlays, and validating overlay documents.
@@ -93,7 +97,7 @@ go install github.com/speakeasy-api/openapi/cmd/openapi@latest
9397

9498
### Usage
9599

96-
The CLI provides three main command groups:
100+
The CLI provides four main command groups:
97101

98102
- **`openapi spec`** - Commands for working with OpenAPI specifications ([documentation](./cmd/openapi/commands/openapi/README.md))
99103
- `bootstrap` - Create a new OpenAPI document with best practice examples
@@ -109,6 +113,10 @@ The CLI provides three main command groups:
109113
- `upgrade` - Upgrade an OpenAPI specification to the latest supported version
110114
- `validate` - Validate an OpenAPI specification document
111115

116+
- **`openapi swagger`** - Commands for working with Swagger 2.0 documents ([documentation](./cmd/openapi/commands/swagger/README.md))
117+
- `validate` - Validate a Swagger 2.0 specification document
118+
- `upgrade` - Upgrade a Swagger 2.0 specification to OpenAPI 3.0
119+
112120
- **`openapi arazzo`** - Commands for working with Arazzo workflow documents ([documentation](./cmd/openapi/commands/arazzo/README.md))
113121
- `validate` - Validate an Arazzo workflow document
114122

@@ -137,6 +145,12 @@ openapi overlay apply --overlay overlay.yaml --schema spec.yaml
137145

138146
# Validate an Arazzo workflow document
139147
openapi arazzo validate ./workflow.arazzo.yaml
148+
149+
# Validate a Swagger 2.0 document
150+
openapi swagger validate ./api.swagger.yaml
151+
152+
# Upgrade Swagger 2.0 to OpenAPI 3.0
153+
openapi swagger upgrade ./api.swagger.yaml ./openapi.yaml
140154
```
141155

142156
For detailed usage instructions for each command group, see the individual documentation linked above.

RELEASE_NOTES.md

Lines changed: 0 additions & 9 deletions
This file was deleted.

arazzo/arazzo_test.go

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ package arazzo_test
22

33
import (
44
"bytes"
5-
"crypto/sha256"
6-
"encoding/hex"
75
"errors"
86
"fmt"
97
"io"
10-
"net/http"
118
"os"
12-
"path/filepath"
139
"strings"
1410
"testing"
1511

@@ -18,6 +14,7 @@ import (
1814
"github.com/speakeasy-api/openapi/arazzo/criterion"
1915
"github.com/speakeasy-api/openapi/expression"
2016
"github.com/speakeasy-api/openapi/extensions"
17+
"github.com/speakeasy-api/openapi/internal/testutils"
2118
"github.com/speakeasy-api/openapi/jsonpointer"
2219
"github.com/speakeasy-api/openapi/jsonschema/oas3"
2320
jsonschema_core "github.com/speakeasy-api/openapi/jsonschema/oas3/core"
@@ -726,7 +723,7 @@ func TestArazzo_StressTests_Validate(t *testing.T) {
726723
require.NoError(t, err)
727724
} else {
728725
var err error
729-
r, err = downloadFile(tt.args.location)
726+
r, err = testutils.DownloadFile(tt.args.location, "ARAZZO_CACHE_DIR", "speakeasy-api_arazzo")
730727
require.NoError(t, err)
731728
}
732729
defer r.Close()
@@ -759,7 +756,7 @@ func TestArazzo_StressTests_RoundTrip(t *testing.T) {
759756
require.NoError(t, err)
760757
} else {
761758
var err error
762-
r, err = downloadFile(tt.args.location)
759+
r, err = testutils.DownloadFile(tt.args.location, "ARAZZO_CACHE_DIR", "speakeasy-api_arazzo")
763760
require.NoError(t, err)
764761
}
765762
defer r.Close()
@@ -787,58 +784,6 @@ func TestArazzo_StressTests_RoundTrip(t *testing.T) {
787784
}
788785
}
789786

790-
func downloadFile(url string) (io.ReadCloser, error) {
791-
// Use environment variable for cache directory, fallback to system temp dir
792-
cacheDir := os.Getenv("ARAZZO_CACHE_DIR")
793-
if cacheDir == "" {
794-
cacheDir = os.TempDir()
795-
}
796-
tempDir := filepath.Join(cacheDir, "speakeasy-api_arazzo")
797-
798-
if err := os.MkdirAll(tempDir, os.ModePerm); err != nil {
799-
return nil, err
800-
}
801-
802-
// hash url to create a unique filename
803-
hash := sha256.Sum256([]byte(url))
804-
filename := hex.EncodeToString(hash[:])
805-
806-
filepath := filepath.Join(tempDir, filename)
807-
808-
// check if file exists and return it otherwise download it
809-
r, err := os.Open(filepath)
810-
if err == nil {
811-
return r, nil
812-
}
813-
814-
resp, err := http.Get(url)
815-
if err != nil {
816-
return nil, err
817-
}
818-
defer resp.Body.Close()
819-
820-
// Read all data from response body
821-
data, err := io.ReadAll(resp.Body)
822-
if err != nil {
823-
return nil, err
824-
}
825-
826-
// Write data to cache file
827-
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0o644)
828-
if err != nil {
829-
return nil, err
830-
}
831-
defer f.Close()
832-
833-
_, err = f.Write(data)
834-
if err != nil {
835-
return nil, err
836-
}
837-
838-
// Return the data as a ReadCloser
839-
return io.NopCloser(bytes.NewReader(data)), nil
840-
}
841-
842787
func roundTripYamlOnly(data []byte) ([]byte, error) {
843788
var node yaml.Node
844789

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package core
2+
3+
import (
4+
"testing"
5+
6+
"github.com/speakeasy-api/openapi/pointer"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
func TestCriterionTypeUnion_SyncChanges_WithStringType_Success(t *testing.T) {
13+
t.Parallel()
14+
15+
yamlContent := "simple"
16+
var node yaml.Node
17+
err := yaml.Unmarshal([]byte(yamlContent), &node)
18+
require.NoError(t, err, "unmarshal should succeed")
19+
20+
var ctu CriterionTypeUnion
21+
validationErrs, err := ctu.Unmarshal(t.Context(), "test", node.Content[0])
22+
require.NoError(t, err, "unmarshal should succeed")
23+
require.Empty(t, validationErrs, "validation errors should be empty")
24+
25+
model := CriterionTypeUnion{
26+
Type: pointer.From("simple"),
27+
}
28+
29+
resultNode, err := ctu.SyncChanges(t.Context(), model, node.Content[0])
30+
require.NoError(t, err, "SyncChanges should succeed")
31+
assert.NotNil(t, resultNode, "result node should not be nil")
32+
}
33+
34+
func TestCriterionTypeUnion_SyncChanges_NonStruct_Error(t *testing.T) {
35+
t.Parallel()
36+
37+
var node yaml.Node
38+
err := yaml.Unmarshal([]byte("simple"), &node)
39+
require.NoError(t, err, "unmarshal should succeed")
40+
41+
ctu := CriterionTypeUnion{}
42+
_, err = ctu.SyncChanges(t.Context(), "not a struct", node.Content[0])
43+
require.Error(t, err, "SyncChanges should fail")
44+
assert.Contains(t, err.Error(), "CriterionTypeUnion.SyncChanges expected a struct, got string", "error message should match")
45+
}

arazzo/core/reusable_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package core
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
func TestReusable_Unmarshal_WithReference_Success(t *testing.T) {
12+
t.Parallel()
13+
14+
yamlContent := `reference: '#/components/parameters/userId'`
15+
16+
var node yaml.Node
17+
err := yaml.Unmarshal([]byte(yamlContent), &node)
18+
require.NoError(t, err, "unmarshal should succeed")
19+
20+
var reusable Reusable[*Parameter]
21+
validationErrs, err := reusable.Unmarshal(t.Context(), "test", node.Content[0])
22+
require.NoError(t, err, "unmarshal should succeed")
23+
require.Empty(t, validationErrs, "validation errors should be empty")
24+
assert.True(t, reusable.GetValid(), "reusable should be valid")
25+
assert.True(t, reusable.Reference.Present, "reference should be present")
26+
assert.NotNil(t, reusable.Reference.Value, "reference value should not be nil")
27+
}
28+
29+
func TestReusable_Unmarshal_NonMappingNode_Error(t *testing.T) {
30+
t.Parallel()
31+
32+
yamlContent := "- item1\n- item2"
33+
34+
var node yaml.Node
35+
err := yaml.Unmarshal([]byte(yamlContent), &node)
36+
require.NoError(t, err, "unmarshal should succeed")
37+
38+
var reusable Reusable[*Parameter]
39+
validationErrs, err := reusable.Unmarshal(t.Context(), "test", node.Content[0])
40+
require.NoError(t, err, "unmarshal error should be nil")
41+
require.NotEmpty(t, validationErrs, "validation errors should not be empty")
42+
assert.Contains(t, validationErrs[0].Error(), "reusable expected object", "error message should match")
43+
assert.False(t, reusable.GetValid(), "reusable should not be valid")
44+
}
45+
46+
func TestReusable_SyncChanges_NonStruct_Error(t *testing.T) {
47+
t.Parallel()
48+
49+
var node yaml.Node
50+
err := yaml.Unmarshal([]byte(`reference: '#/test'`), &node)
51+
require.NoError(t, err, "unmarshal should succeed")
52+
53+
reusable := Reusable[*Parameter]{}
54+
_, err = reusable.SyncChanges(t.Context(), "not a struct", node.Content[0])
55+
require.Error(t, err, "SyncChanges should fail")
56+
assert.Contains(t, err.Error(), "Reusable.SyncChanges expected a struct, got string", "error message should match")
57+
}

0 commit comments

Comments
 (0)