Skip to content

Commit 779ac8a

Browse files
feat: add clean and optimize commands for OpenAPI specifications (#36)
1 parent e76c7aa commit 779ac8a

38 files changed

+5022
-163
lines changed

.github/dependabot.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ updates:
88
day: "monday"
99
time: "09:00"
1010
open-pull-requests-limit: 10
11-
reviewers:
12-
- "speakeasy-api/maintainers"
1311
assignees:
1412
- "speakeasy-api/maintainers"
1513
commit-message:
16-
prefix: "deps"
17-
prefix-development: "deps"
14+
prefix: "fix"
15+
prefix-development: "chore"
1816
include: "scope"
1917
labels:
2018
- "dependencies"
@@ -42,8 +40,6 @@ updates:
4240
day: "monday"
4341
time: "09:00"
4442
open-pull-requests-limit: 5
45-
reviewers:
46-
- "speakeasy-api/maintainers"
4743
assignees:
4844
- "speakeasy-api/maintainers"
4945
commit-message:

.mise.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ gotestsum = "latest"
66
[tasks.setup-vscode-symlinks]
77
description = "Create VSCode symlinks for tools not automatically handled by mise-vscode"
88
run = [
9-
"mkdir -p .vscode/mise-tools",
10-
"ln -sf $(mise exec [email protected] -- which golangci-lint) .vscode/mise-tools/golangci-lint",
9+
"mkdir -p .vscode/mise-tools",
10+
"ln -sf $(mise exec [email protected] -- which golangci-lint) .vscode/mise-tools/golangci-lint",
1111
]
1212

1313
[hooks]
1414
postinstall = [
15-
"mise run setup-vscode-symlinks",
16-
"go install go.uber.org/nilaway/cmd/nilaway@8ad05f0",
15+
"mise run setup-vscode-symlinks",
16+
"go install go.uber.org/nilaway/cmd/nilaway@8ad05f0",
1717
]

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,22 @@ go install github.com/speakeasy-api/openapi/cmd/openapi@latest
9090
The CLI provides three main command groups:
9191

9292
- **`openapi spec`** - Commands for working with OpenAPI specifications ([documentation](./openapi/cmd/README.md))
93+
- `bootstrap` - Create a new OpenAPI document with best practice examples
94+
- `bundle` - Bundle external references into components section
95+
- `clean` - Remove unused components from an OpenAPI specification
96+
- `inline` - Inline all references in an OpenAPI specification
97+
- `join` - Join multiple OpenAPI documents into a single document
98+
- `optimize` - Optimize an OpenAPI specification by deduplicating inline schemas
99+
- `upgrade` - Upgrade an OpenAPI specification to the latest supported version
100+
- `validate` - Validate an OpenAPI specification document
101+
93102
- **`openapi arazzo`** - Commands for working with Arazzo workflow documents ([documentation](./arazzo/cmd/README.md))
103+
- `validate` - Validate an Arazzo workflow document
104+
94105
- **`openapi overlay`** - Commands for working with OpenAPI overlays ([documentation](./overlay/cmd/README.md))
106+
- `apply` - Apply an overlay to an OpenAPI specification
107+
- `compare` - Compare two specifications and generate an overlay describing differences
108+
- `validate` - Validate an OpenAPI overlay document
95109

96110
#### Quick Examples
97111

arazzo/successaction.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func validationActionWorkflowIDAndStepID(ctx context.Context, parentName string,
160160
// Get the parent match function from the location
161161
parentLoc := item.Location[len(item.Location)-1]
162162

163-
err := parentLoc.Parent(Matcher{
163+
err := parentLoc.ParentMatchFunc(Matcher{
164164
Workflow: func(workflow *Workflow) error {
165165
return item.Match(Matcher{
166166
Step: func(step *Step) error {

arazzo/walk.go

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,24 @@ func walk(ctx context.Context, arazzo *Arazzo, yield func(WalkItem) bool) {
6868
// Visit each of the top level fields in turn populating their location context with field and any key/index information
6969
loc := Locations{}
7070

71-
if !walkInfo(ctx, &arazzo.Info, append(loc, LocationContext{Parent: arazzoMatchFunc, ParentField: "info"}), arazzo, yield) {
71+
if !walkInfo(ctx, &arazzo.Info, append(loc, LocationContext{ParentMatchFunc: arazzoMatchFunc, ParentField: "info"}), arazzo, yield) {
7272
return
7373
}
7474

75-
if !walkSourceDescriptions(ctx, arazzo.SourceDescriptions, append(loc, LocationContext{Parent: arazzoMatchFunc, ParentField: "sourceDescriptions"}), arazzo, yield) {
75+
if !walkSourceDescriptions(ctx, arazzo.SourceDescriptions, append(loc, LocationContext{ParentMatchFunc: arazzoMatchFunc, ParentField: "sourceDescriptions"}), arazzo, yield) {
7676
return
7777
}
7878

79-
if !walkWorkflows(ctx, arazzo.Workflows, append(loc, LocationContext{Parent: arazzoMatchFunc, ParentField: "workflows"}), arazzo, yield) {
79+
if !walkWorkflows(ctx, arazzo.Workflows, append(loc, LocationContext{ParentMatchFunc: arazzoMatchFunc, ParentField: "workflows"}), arazzo, yield) {
8080
return
8181
}
8282

83-
if !walkComponents(ctx, arazzo.Components, append(loc, LocationContext{Parent: arazzoMatchFunc, ParentField: "components"}), arazzo, yield) {
83+
if !walkComponents(ctx, arazzo.Components, append(loc, LocationContext{ParentMatchFunc: arazzoMatchFunc, ParentField: "components"}), arazzo, yield) {
8484
return
8585
}
8686

8787
// Visit Arazzo Extensions
88-
yield(WalkItem{Match: getMatchFunc(arazzo.Extensions), Location: append(loc, LocationContext{Parent: arazzoMatchFunc, ParentField: ""}), Arazzo: arazzo})
88+
yield(WalkItem{Match: getMatchFunc(arazzo.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: arazzoMatchFunc, ParentField: ""}), Arazzo: arazzo})
8989
}
9090

9191
func walkInfo(_ context.Context, info *Info, loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -100,7 +100,7 @@ func walkInfo(_ context.Context, info *Info, loc Locations, arazzo *Arazzo, yiel
100100
}
101101

102102
// Visit Info Extensions
103-
return yield(WalkItem{Match: getMatchFunc(info.Extensions), Location: append(loc, LocationContext{Parent: infoMatchFunc, ParentField: ""}), Arazzo: arazzo})
103+
return yield(WalkItem{Match: getMatchFunc(info.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: infoMatchFunc, ParentField: ""}), Arazzo: arazzo})
104104
}
105105

106106
func walkSourceDescriptions(ctx context.Context, sourceDescriptions []*SourceDescription, loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -134,7 +134,7 @@ func walkSourceDescription(_ context.Context, sd *SourceDescription, loc Locatio
134134
}
135135

136136
// Visit SourceDescription Extensions
137-
return yield(WalkItem{Match: getMatchFunc(sd.Extensions), Location: append(loc, LocationContext{Parent: sdMatchFunc, ParentField: ""}), Arazzo: arazzo})
137+
return yield(WalkItem{Match: getMatchFunc(sd.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: sdMatchFunc, ParentField: ""}), Arazzo: arazzo})
138138
}
139139

140140
func walkWorkflows(ctx context.Context, workflows []*Workflow, loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -168,32 +168,32 @@ func walkWorkflow(ctx context.Context, workflow *Workflow, loc Locations, arazzo
168168
}
169169

170170
// Walk through parameters
171-
if !walkReusableParameters(ctx, workflow.Parameters, append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: "parameters"}), arazzo, yield) {
171+
if !walkReusableParameters(ctx, workflow.Parameters, append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: "parameters"}), arazzo, yield) {
172172
return false
173173
}
174174

175175
// Walk through inputs schema using oas3 walking
176-
if !walkJSONSchema(ctx, workflow.Inputs, append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: "inputs"}), arazzo, yield) {
176+
if !walkJSONSchema(ctx, workflow.Inputs, append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: "inputs"}), arazzo, yield) {
177177
return false
178178
}
179179

180180
// Walk through steps
181-
if !walkSteps(ctx, workflow.Steps, append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: "steps"}), arazzo, yield) {
181+
if !walkSteps(ctx, workflow.Steps, append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: "steps"}), arazzo, yield) {
182182
return false
183183
}
184184

185185
// Walk through success actions
186-
if !walkReusableSuccessActions(ctx, workflow.SuccessActions, append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: "successActions"}), arazzo, yield) {
186+
if !walkReusableSuccessActions(ctx, workflow.SuccessActions, append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: "successActions"}), arazzo, yield) {
187187
return false
188188
}
189189

190190
// Walk through failure actions
191-
if !walkReusableFailureActions(ctx, workflow.FailureActions, append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: "failureActions"}), arazzo, yield) {
191+
if !walkReusableFailureActions(ctx, workflow.FailureActions, append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: "failureActions"}), arazzo, yield) {
192192
return false
193193
}
194194

195195
// Visit Workflow Extensions
196-
return yield(WalkItem{Match: getMatchFunc(workflow.Extensions), Location: append(loc, LocationContext{Parent: workflowMatchFunc, ParentField: ""}), Arazzo: arazzo})
196+
return yield(WalkItem{Match: getMatchFunc(workflow.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: workflowMatchFunc, ParentField: ""}), Arazzo: arazzo})
197197
}
198198

199199
func walkReusableParameters(ctx context.Context, parameters []*ReusableParameter, loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -273,10 +273,10 @@ func convertSchemaLocation(schemaLoc walkpkg.Locations[oas3.SchemaMatchFunc], ba
273273
// Convert each oas3 location context to arazzo location context
274274
for _, schemaLocCtx := range schemaLoc {
275275
result = append(result, LocationContext{
276-
Parent: convertSchemaMatchFunc(schemaLocCtx.Parent),
277-
ParentField: schemaLocCtx.ParentField,
278-
ParentKey: schemaLocCtx.ParentKey,
279-
ParentIndex: schemaLocCtx.ParentIndex,
276+
ParentMatchFunc: convertSchemaMatchFunc(schemaLocCtx.ParentMatchFunc),
277+
ParentField: schemaLocCtx.ParentField,
278+
ParentKey: schemaLocCtx.ParentKey,
279+
ParentIndex: schemaLocCtx.ParentIndex,
280280
})
281281
}
282282

@@ -314,22 +314,22 @@ func walkStep(ctx context.Context, step *Step, loc Locations, arazzo *Arazzo, yi
314314
}
315315

316316
// Walk through parameters
317-
if !walkReusableParameters(ctx, step.Parameters, append(loc, LocationContext{Parent: stepMatchFunc, ParentField: "parameters"}), arazzo, yield) {
317+
if !walkReusableParameters(ctx, step.Parameters, append(loc, LocationContext{ParentMatchFunc: stepMatchFunc, ParentField: "parameters"}), arazzo, yield) {
318318
return false
319319
}
320320

321321
// Walk through success actions
322-
if !walkReusableSuccessActions(ctx, step.OnSuccess, append(loc, LocationContext{Parent: stepMatchFunc, ParentField: "onSuccess"}), arazzo, yield) {
322+
if !walkReusableSuccessActions(ctx, step.OnSuccess, append(loc, LocationContext{ParentMatchFunc: stepMatchFunc, ParentField: "onSuccess"}), arazzo, yield) {
323323
return false
324324
}
325325

326326
// Walk through failure actions
327-
if !walkReusableFailureActions(ctx, step.OnFailure, append(loc, LocationContext{Parent: stepMatchFunc, ParentField: "onFailure"}), arazzo, yield) {
327+
if !walkReusableFailureActions(ctx, step.OnFailure, append(loc, LocationContext{ParentMatchFunc: stepMatchFunc, ParentField: "onFailure"}), arazzo, yield) {
328328
return false
329329
}
330330

331331
// Visit Step Extensions
332-
return yield(WalkItem{Match: getMatchFunc(step.Extensions), Location: append(loc, LocationContext{Parent: stepMatchFunc, ParentField: ""}), Arazzo: arazzo})
332+
return yield(WalkItem{Match: getMatchFunc(step.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: stepMatchFunc, ParentField: ""}), Arazzo: arazzo})
333333
}
334334

335335
func walkReusableSuccessActions(ctx context.Context, actions []*ReusableSuccessAction, loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -414,27 +414,27 @@ func walkComponents(ctx context.Context, components *Components, loc Locations,
414414
}
415415

416416
// Walk through inputs
417-
if !walkComponentInputs(ctx, components.Inputs, append(loc, LocationContext{Parent: componentsMatchFunc, ParentField: "inputs"}), arazzo, yield) {
417+
if !walkComponentInputs(ctx, components.Inputs, append(loc, LocationContext{ParentMatchFunc: componentsMatchFunc, ParentField: "inputs"}), arazzo, yield) {
418418
return false
419419
}
420420

421421
// Walk through parameters
422-
if !walkComponentParameters(ctx, components.Parameters, append(loc, LocationContext{Parent: componentsMatchFunc, ParentField: "parameters"}), arazzo, yield) {
422+
if !walkComponentParameters(ctx, components.Parameters, append(loc, LocationContext{ParentMatchFunc: componentsMatchFunc, ParentField: "parameters"}), arazzo, yield) {
423423
return false
424424
}
425425

426426
// Walk through success actions
427-
if !walkComponentSuccessActions(ctx, components.SuccessActions, append(loc, LocationContext{Parent: componentsMatchFunc, ParentField: "successActions"}), arazzo, yield) {
427+
if !walkComponentSuccessActions(ctx, components.SuccessActions, append(loc, LocationContext{ParentMatchFunc: componentsMatchFunc, ParentField: "successActions"}), arazzo, yield) {
428428
return false
429429
}
430430

431431
// Walk through failure actions
432-
if !walkComponentFailureActions(ctx, components.FailureActions, append(loc, LocationContext{Parent: componentsMatchFunc, ParentField: "failureActions"}), arazzo, yield) {
432+
if !walkComponentFailureActions(ctx, components.FailureActions, append(loc, LocationContext{ParentMatchFunc: componentsMatchFunc, ParentField: "failureActions"}), arazzo, yield) {
433433
return false
434434
}
435435

436436
// Visit Components Extensions
437-
return yield(WalkItem{Match: getMatchFunc(components.Extensions), Location: append(loc, LocationContext{Parent: componentsMatchFunc, ParentField: ""}), Arazzo: arazzo})
437+
return yield(WalkItem{Match: getMatchFunc(components.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: componentsMatchFunc, ParentField: ""}), Arazzo: arazzo})
438438
}
439439

440440
func walkComponentInputs(ctx context.Context, inputs *sequencedmap.Map[string, *oas3.JSONSchema[oas3.Referenceable]], loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -487,7 +487,7 @@ func walkParameter(_ context.Context, parameter *Parameter, loc Locations, arazz
487487
}
488488

489489
// Visit Parameter Extensions
490-
return yield(WalkItem{Match: getMatchFunc(parameter.Extensions), Location: append(loc, LocationContext{Parent: parameterMatchFunc, ParentField: ""}), Arazzo: arazzo})
490+
return yield(WalkItem{Match: getMatchFunc(parameter.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: parameterMatchFunc, ParentField: ""}), Arazzo: arazzo})
491491
}
492492

493493
func walkComponentSuccessActions(ctx context.Context, actions *sequencedmap.Map[string, *SuccessAction], loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -521,7 +521,7 @@ func walkSuccessAction(_ context.Context, action *SuccessAction, loc Locations,
521521
}
522522

523523
// Visit SuccessAction Extensions
524-
return yield(WalkItem{Match: getMatchFunc(action.Extensions), Location: append(loc, LocationContext{Parent: actionMatchFunc, ParentField: ""}), Arazzo: arazzo})
524+
return yield(WalkItem{Match: getMatchFunc(action.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: actionMatchFunc, ParentField: ""}), Arazzo: arazzo})
525525
}
526526

527527
func walkComponentFailureActions(ctx context.Context, actions *sequencedmap.Map[string, *FailureAction], loc Locations, arazzo *Arazzo, yield func(WalkItem) bool) bool {
@@ -555,7 +555,7 @@ func walkFailureAction(_ context.Context, action *FailureAction, loc Locations,
555555
}
556556

557557
// Visit FailureAction Extensions
558-
return yield(WalkItem{Match: getMatchFunc(action.Extensions), Location: append(loc, LocationContext{Parent: actionMatchFunc, ParentField: ""}), Arazzo: arazzo})
558+
return yield(WalkItem{Match: getMatchFunc(action.Extensions), Location: append(loc, LocationContext{ParentMatchFunc: actionMatchFunc, ParentField: ""}), Arazzo: arazzo})
559559
}
560560

561561
type matchHandler[T any] struct {

jsonpointer/models.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ func navigateModel(sourceVal reflect.Value, currentPart navigationPart, stack []
4646

4747
key := currentPart.unescapeValue()
4848

49-
// First, check if this is an embedded map (anonymous field that implements sequenced map interface)
50-
// This follows the same pattern as in marshaller/unmarshaller.go
5149
sourceType := sourceVal.Type()
5250
if sourceType.Kind() == reflect.Ptr {
5351
sourceType = sourceType.Elem()

jsonschema/oas3/schema.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,9 @@ func (s *Schema) IsEqual(other *Schema) bool {
673673
// Both nil, continue
674674
case s.Type == nil || other.Type == nil:
675675
return false
676-
case !s.Type.IsEqual(other.Type):
676+
}
677+
// Compare both type arrays have the same types
678+
if !equalSlices(s.GetType(), other.GetType()) {
677679
return false
678680
}
679681

@@ -806,7 +808,7 @@ func (s *Schema) IsEqual(other *Schema) bool {
806808
}
807809

808810
// Compare string slices
809-
if !equalStringSlices(s.Required, other.Required) {
811+
if !equalSlices(s.Required, other.Required) {
810812
return false
811813
}
812814

@@ -980,16 +982,16 @@ func equalSequencedMaps(a, b *sequencedmap.Map[string, *JSONSchema[Referenceable
980982
return a.IsEqualFunc(b, equalJSONSchemas)
981983
}
982984

983-
func equalStringSlices(a, b []string) bool {
985+
func equalSlices[T any](a, b []T) bool {
984986
// Treat nil and empty slices as equal
985987
if len(a) == 0 && len(b) == 0 {
986988
return true
987989
}
988990
if len(a) != len(b) {
989991
return false
990992
}
991-
for i, itemA := range a {
992-
if itemA != b[i] {
993+
for i := range a {
994+
if !reflect.DeepEqual(a[i], b[i]) {
993995
return false
994996
}
995997
}

0 commit comments

Comments
 (0)