Skip to content

Commit 3b71427

Browse files
fix: add support for arazzo specification 1.0.1 (#10)
1 parent 92ded3c commit 3b71427

7 files changed

+104
-37
lines changed

arazzo/arazzo.go

+43-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"fmt"
1010
"io"
1111
"slices"
12+
"strconv"
13+
"strings"
1214

1315
"github.com/speakeasy-api/openapi/arazzo/core"
1416
"github.com/speakeasy-api/openapi/extensions"
@@ -18,7 +20,12 @@ import (
1820
)
1921

2022
// Version is the version of the Arazzo Specification that this package conforms to.
21-
const Version = "1.0.0"
23+
const (
24+
Version = "1.0.1"
25+
VersionMajor = 1
26+
VersionMinor = 0
27+
VersionPatch = 1
28+
)
2229

2330
// Arazzo is the root object for an Arazzo document.
2431
type Arazzo struct {
@@ -148,9 +155,18 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
148155

149156
errs := []error{}
150157

151-
if a.Arazzo != Version {
158+
arazzoMajor, arazzoMinor, arazzoPatch, err := parseVersion(a.Arazzo)
159+
if err != nil {
152160
errs = append(errs, &validation.Error{
153-
Message: "Arazzo version must be 1.0.0",
161+
Message: fmt.Sprintf("invalid Arazzo version in document %s: %s", a.Arazzo, err.Error()),
162+
Line: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Line,
163+
Column: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Column,
164+
})
165+
}
166+
167+
if arazzoMajor != VersionMajor || arazzoMinor != VersionMinor || arazzoPatch > VersionPatch {
168+
errs = append(errs, &validation.Error{
169+
Message: fmt.Sprintf("Only Arazzo version %s and below is supported", Version),
154170
Line: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Line,
155171
Column: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Column,
156172
})
@@ -200,3 +216,27 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
200216

201217
return errs
202218
}
219+
220+
func parseVersion(version string) (int, int, int, error) {
221+
parts := strings.Split(version, ".")
222+
if len(parts) != 3 {
223+
return 0, 0, 0, fmt.Errorf("invalid version %s", version)
224+
}
225+
226+
major, err := strconv.Atoi(parts[0])
227+
if err != nil {
228+
return 0, 0, 0, fmt.Errorf("invalid major version %s: %w", parts[0], err)
229+
}
230+
231+
minor, err := strconv.Atoi(parts[1])
232+
if err != nil {
233+
return 0, 0, 0, fmt.Errorf("invalid minor version %s: %w", parts[1], err)
234+
}
235+
236+
patch, err := strconv.Atoi(parts[2])
237+
if err != nil {
238+
return 0, 0, 0, fmt.Errorf("invalid patch version %s: %w", parts[2], err)
239+
}
240+
241+
return major, minor, patch, nil
242+
}

arazzo/arazzo_examples_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func Example_creating() {
5353
Info: arazzo.Info{
5454
Title: "My Workflow",
5555
Summary: pointer.From("A summary"),
56-
Version: "1.0.0",
56+
Version: "1.0.1",
5757
},
5858
// ...
5959
}

arazzo/arazzo_test.go

+18-27
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ func TestArazzo_RoundTrip_Success(t *testing.T) {
237237
}
238238

239239
func TestArazzoUnmarshal_ValidationErrors(t *testing.T) {
240-
data := []byte(`arazzo: 1.0.1
240+
data := []byte(`arazzo: 1.0.2
241241
x-test: some-value
242242
info:
243243
title: My Workflow
@@ -254,14 +254,14 @@ sourceDescriptions:
254254

255255
assert.Equal(t, []error{
256256
&validation.Error{Line: 1, Column: 1, Message: "field workflows is missing"},
257-
&validation.Error{Line: 1, Column: 9, Message: "Arazzo version must be 1.0.0"},
257+
&validation.Error{Line: 1, Column: 9, Message: "Only Arazzo version 1.0.1 and below is supported"},
258258
&validation.Error{Line: 4, Column: 3, Message: "field version is missing"},
259259
&validation.Error{Line: 6, Column: 5, Message: "field url is missing"},
260260
&validation.Error{Line: 7, Column: 11, Message: "type must be one of [openapi, arazzo]"},
261261
}, validationErrs)
262262

263263
expected := &arazzo.Arazzo{
264-
Arazzo: "1.0.1",
264+
Arazzo: "1.0.2",
265265
Info: arazzo.Info{
266266
Title: "My Workflow",
267267
Version: "",
@@ -307,6 +307,7 @@ func TestArazzo_Mutate_Success(t *testing.T) {
307307
require.NoError(t, err)
308308
require.Empty(t, validationErrs)
309309

310+
a.Arazzo = "1.0.0"
310311
a.Info.Title = "My updated workflow title"
311312
sd := a.SourceDescriptions[0]
312313
sd.Extensions.Set("x-test", yml.CreateOrUpdateScalarNode(ctx, "some-value", nil))
@@ -478,53 +479,49 @@ var stressTests = []struct {
478479
{
479480
name: "Redocly Museum API",
480481
args: args{
481-
location: "https://raw.githubusercontent.com/Redocly/museum-openapi-example/091a853a0d2467bc4c65bb6f615a33d0a7201747/arazzo/museum-api.arazzo.yaml",
482-
validationIgnores: []string{
483-
"invalid jsonpath expression", // they have criterion marked as jsonpath but uses a simple condition instead
484-
},
482+
location: "https://raw.githubusercontent.com/Redocly/museum-openapi-example/2770b2b2e59832d245c7b0eb0badf6568d7efb53/arazzo/museum-api.arazzo.yaml",
485483
},
486484
wantTitle: "Redocly Museum API Test Workflow",
487485
},
488486
{
489487
name: "Redocly Museum Tickets",
490488
args: args{
491-
location: "https://raw.githubusercontent.com/Redocly/museum-openapi-example/091a853a0d2467bc4c65bb6f615a33d0a7201747/arazzo/museum-tickets.arazzo.yaml",
489+
location: "https://raw.githubusercontent.com/Redocly/museum-openapi-example/2770b2b2e59832d245c7b0eb0badf6568d7efb53/arazzo/museum-tickets.arazzo.yaml",
492490
},
493491
wantTitle: "Redocly Museum Tickets Workflow",
494492
},
495493
{
496494
name: "Redocly Warp API",
497495
args: args{
498-
// TODO line 25 report inconsistency with spec and value
499-
location: "https://raw.githubusercontent.com/Redocly/warp-single-sidebar/be5f885db3cdd9c595f9a7e724c04e9f6a0b70dd/apis/arazzo.yaml",
496+
location: "https://raw.githubusercontent.com/Redocly/warp-single-sidebar/b78fc09da52d7755e92e1bc8f990edd37421cbde/apis/arazzo.yaml",
500497
},
501498
wantTitle: "Warp API",
502499
},
503500
{
504501
name: "Arazzo Extended Parameters Example",
505502
args: args{
506-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/ExtendedParametersExample.arazzo.yaml",
503+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/ExtendedParametersExample.arazzo.yaml",
507504
},
508505
wantTitle: "Public Zoo API",
509506
},
510507
{
511508
name: "Arazzo FAPI-PAR Example",
512509
args: args{
513-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/FAPI-PAR.arazzo.yaml",
510+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/FAPI-PAR.arazzo.yaml",
514511
},
515512
wantTitle: "PAR, Authorization and Token workflow",
516513
},
517514
{
518515
name: "Arazzo Login and Retrieve Pets Example",
519516
args: args{
520-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/LoginAndRetrievePets.arazzo.yaml",
517+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/LoginAndRetrievePets.arazzo.yaml",
521518
},
522519
wantTitle: "A pet purchasing workflow",
523520
},
524521
{
525522
name: "Arazzo BNPL Example",
526523
args: args{
527-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/bnpl-arazzo.yaml",
524+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/bnpl-arazzo.yaml",
528525
validationIgnores: []string{
529526
"$response.headers.Location", // doc should be referencing `$response.header.Location`
530527
},
@@ -534,18 +531,14 @@ var stressTests = []struct {
534531
{
535532
name: "Arazzo OAuth Example",
536533
args: args{
537-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/oauth.arazzo.yaml",
534+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/oauth.arazzo.yaml",
538535
},
539536
wantTitle: "Example OAuth service",
540537
},
541538
{
542539
name: "Arazzo Pet Coupons Example",
543540
args: args{
544-
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/977f586da14b65bd8e612b763267b8b728749e52/examples/1.0.0/pet-coupons.arazzo.yaml",
545-
validationIgnores: []string{
546-
"$outputs[0]", // legit issue trying to reference outputs by index
547-
"$workflow_order_id", // legit issue trying to reference workflow_order_id
548-
},
541+
location: "https://raw.githubusercontent.com/OAI/Arazzo-Specification/23852b8b0d13ab1e3288a57a990611ffed45ab5d/examples/1.0.0/pet-coupons.arazzo.yaml",
549542
},
550543
wantTitle: "Petstore - Apply Coupons",
551544
},
@@ -582,7 +575,7 @@ var stressTests = []struct {
582575
{
583576
name: "Itarazzo Client Pet Store Example",
584577
args: args{
585-
location: "https://raw.githubusercontent.com/leidenheit/itarazzo-client/b744ca1ca3a036964ae30be601f10a25b14dc52d/src/test/resources/pet-store.arazzo.yaml",
578+
location: "https://raw.githubusercontent.com/leidenheit/itarazzo-client/b3c126d28bf80ae7d74861c08509be33b83c5ddf/src/test/resources/pet-store.arazzo.yaml",
586579
validationIgnores: []string{
587580
"jsonpointer must start with /: $.status", // legit issues TODO: improve the error returned as it is wrong
588581
"jsonpointer must start with /: $.id", // legit issues TODO: improve the error returned as it is wrong
@@ -593,15 +586,14 @@ var stressTests = []struct {
593586
{
594587
name: "Ritza build-a-bot workflow",
595588
args: args{
596-
location: "https://raw.githubusercontent.com/ritza-co/e2e-testing-arazzo/c0615c3708a1e4c0fcaeb79edae78ddc4eb5ba82/arazzo.yaml",
597-
validationIgnores: []string{},
589+
location: "https://raw.githubusercontent.com/ritza-co/e2e-testing-arazzo/c0615c3708a1e4c0fcaeb79edae78ddc4eb5ba82/arazzo.yaml",
598590
},
599591
wantTitle: "Build-a-Bot Workflow",
600592
},
601593
{
602594
name: " API-Flows adyen-giving workflow",
603595
args: args{
604-
location: "https://raw.githubusercontent.com/API-Flows/openapi-workflow-registry/3d85d79232fa8f42993b2f5bd47e273b9369dc2d/root/adyen/adyen-giving.yaml",
596+
location: "https://raw.githubusercontent.com/API-Flows/openapi-workflow-registry/75c237ce1b155ba9f8dc7f065759df7ae1cbbbe5/root/adyen/adyen-giving.yaml",
605597
validationIgnores: []string{
606598
"in must be one of [path, query, header, cookie] but was body",
607599
},
@@ -611,16 +603,15 @@ var stressTests = []struct {
611603
{
612604
name: "API-Flows simple workflow",
613605
args: args{
614-
location: "https://raw.githubusercontent.com/API-Flows/openapi-workflow-parser/6b28ba4def262969c5a96bc54d08433e6c336643/src/test/resources/1.0.0/simple.workflow.yaml",
615-
validationIgnores: []string{},
606+
location: "https://raw.githubusercontent.com/API-Flows/openapi-workflow-parser/6b28ba4def262969c5a96bc54d08433e6c336643/src/test/resources/1.0.0/simple.workflow.yaml",
616607
},
617608
wantTitle: "simple",
618609
},
619610
// Disabled for now as it is currently failing round tripping due to missing conditions
620611
// {
621612
// name: "Kartikhub swap tokens workflow",
622613
// args: args{
623-
// location: "https://raw.githubusercontent.com/Kartikhub/web3-basics/d95bc51bb935ef07d627e52c6fdfe18aaea69e18/swap-react/docs/swap-transaction-arazzo.yaml",
614+
// location: "https://raw.githubusercontent.com/Kartikhub/web3-basics/be13fa7e6fdf386eef08bba2843d4a8b615561b9/swap-react/docs/swap-transaction-arazzo.yaml",
624615
// validationIgnores: []string{ // All valid issues
625616
// "field condition is missing",
626617
// "condition is required",

arazzo/expression/expression.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package expression
33
import (
44
"fmt"
55
"regexp"
6+
"slices"
67
"strings"
78

89
"github.com/speakeasy-api/openapi/jsonpointer"
@@ -150,7 +151,14 @@ func (e Expression) Validate(validateAsExpression bool) error {
150151
return err
151152
}
152153

153-
if typ == ExpressionTypeSourceDescriptions && strings.HasSuffix(name, "url") {
154+
switch {
155+
case typ == ExpressionTypeSourceDescriptions && strings.HasSuffix(name, "url"):
156+
allowJsonPointers = true
157+
case slices.Contains([]ExpressionType{ExpressionTypeSteps, ExpressionTypeWorkflows}, typ):
158+
if len(expressionParts) > 0 && expressionParts[0] == "outputs" {
159+
allowJsonPointers = true
160+
}
161+
case typ == ExpressionTypeOutputs:
154162
allowJsonPointers = true
155163
}
156164
default:

arazzo/expression/expression_test.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,41 @@ func TestExpression_Validate_Success(t *testing.T) {
138138
validateAsExpression: true,
139139
},
140140
},
141+
{
142+
name: "outputs with json pointer",
143+
args: args{
144+
e: Expression("$outputs.someOutput#/some/path"),
145+
validateAsExpression: true,
146+
},
147+
},
141148
{
142149
name: "steps",
143150
args: args{
144151
e: Expression("$steps.someStep"),
145152
validateAsExpression: true,
146153
},
147154
},
155+
{
156+
name: "step outputs with json pointer",
157+
args: args{
158+
e: Expression("$steps.someStep.outputs.someOutput#/some/path"),
159+
validateAsExpression: true,
160+
},
161+
},
148162
{
149163
name: "workflows",
150164
args: args{
151165
e: Expression("$workflows.someWorkflow"),
152166
validateAsExpression: true,
153167
},
154168
},
169+
{
170+
name: "workflow outputs with json pointer",
171+
args: args{
172+
e: Expression("$workflows.someWorkflow.outputs.someOutput#/some/path"),
173+
validateAsExpression: true,
174+
},
175+
},
155176
{
156177
name: "source descriptions",
157178
args: args{
@@ -335,11 +356,18 @@ func TestExpression_Validate_Failure(t *testing.T) {
335356
wantErr: errors.New("expression is not valid, expected name after $inputs: $inputs"),
336357
},
337358
{
338-
name: "invalid json pointer expression in context",
359+
name: "invalid json pointer expression in inputs expression",
360+
args: args{
361+
e: Expression("$inputs.someInput#/some/path"),
362+
},
363+
wantErr: errors.New("expression is not valid, json pointers are not allowed in current context: $inputs.someInput#/some/path"),
364+
},
365+
{
366+
name: "invalid json pointer expression in workflow inputs expression",
339367
args: args{
340-
e: Expression("$outputs.someOutput#/some/path"),
368+
e: Expression("$workflows.someWorkflow.inputs.someInput#/some/path"),
341369
},
342-
wantErr: errors.New("expression is not valid, json pointers are not allowed in current context: $outputs.someOutput#/some/path"),
370+
wantErr: errors.New("expression is not valid, json pointers are not allowed in current context: $workflows.someWorkflow.inputs.someInput#/some/path"),
343371
},
344372
}
345373
for _, tt := range tests {

arazzo/expression/expressions_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestExtractExpressions(t *testing.T) {
3535
},
3636
},
3737
{
38-
name: "expression with json pointer",
38+
name: "request body expression with json pointer",
3939
args: args{
4040
expression: "$request.body#/some/path",
4141
},

arazzo/testdata/test.arazzo.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
arazzo: 1.0.0
1+
arazzo: 1.0.1
22
info:
33
title: My Workflow
44
summary: A summary

0 commit comments

Comments
 (0)