Skip to content

Commit 430acc3

Browse files
refactor: remove boiler plate by using embeddable base models that simplify implementation (#14)
1 parent 1046dca commit 430acc3

Some content is hidden

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

66 files changed

+4619
-666
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CLAUDE.local.md
2+
.claude/

arazzo/arazzo.go

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const (
3030

3131
// Arazzo is the root object for an Arazzo document.
3232
type Arazzo struct {
33+
marshaller.Model[core.Arazzo]
34+
3335
// Arazzo is the version of the Arazzo Specification that this document conforms to.
3436
Arazzo string
3537
// Info provides metadata about the Arazzo document.
@@ -42,11 +44,6 @@ type Arazzo struct {
4244
Components *Components
4345
// Extensions provides a list of extensions to the Arazzo document.
4446
Extensions *extensions.Extensions
45-
46-
// Valid indicates whether this model passed validation.
47-
Valid bool
48-
49-
core core.Arazzo
5047
}
5148

5249
var _ interfaces.Model[core.Arazzo] = (*Arazzo)(nil)
@@ -125,29 +122,23 @@ func Marshal(ctx context.Context, arazzo *Arazzo, w io.Writer) error {
125122
return nil
126123
}
127124

128-
// GetCore will return the low level representation of the Arazzo document.
129-
// Useful for accessing line and column numbers for various nodes in the backing yaml/json document.
130-
func (a *Arazzo) GetCore() *core.Arazzo {
131-
return &a.core
132-
}
133-
134125
// Sync will sync any changes made to the Arazzo document models back to the core models.
135126
func (a *Arazzo) Sync(ctx context.Context) error {
136-
if _, err := marshaller.SyncValue(ctx, a, &a.core, nil, false); err != nil {
127+
if _, err := marshaller.SyncValue(ctx, a, a.GetCore(), nil, false); err != nil {
137128
return err
138129
}
139130
return nil
140131
}
141132

142133
// Marshal will marshal the Arazzo document to the provided io.Writer.
143134
func (a *Arazzo) Marshal(ctx context.Context, w io.Writer) error {
144-
ctx = yml.ContextWithConfig(ctx, a.core.Config)
135+
ctx = yml.ContextWithConfig(ctx, a.GetCore().Config)
145136

146-
if _, err := marshaller.SyncValue(ctx, a, &a.core, nil, false); err != nil {
137+
if _, err := marshaller.SyncValue(ctx, a, a.GetCore(), nil, false); err != nil {
147138
return err
148139
}
149140

150-
return a.core.Marshal(ctx, w)
141+
return a.GetCore().Marshal(ctx, w)
151142
}
152143

153144
// Validate will validate the Arazzo document against the Arazzo Specification.
@@ -158,11 +149,11 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
158149

159150
arazzoMajor, arazzoMinor, arazzoPatch, err := parseVersion(a.Arazzo)
160151
if err != nil {
161-
errs = append(errs, validation.NewValueError(fmt.Sprintf("invalid Arazzo version in document %s: %s", a.Arazzo, err.Error()), a.core, a.core.Arazzo))
152+
errs = append(errs, validation.NewValueError(fmt.Sprintf("invalid Arazzo version in document %s: %s", a.Arazzo, err.Error()), a.GetCore(), a.GetCore().Arazzo))
162153
}
163154

164155
if arazzoMajor != VersionMajor || arazzoMinor != VersionMinor || arazzoPatch > VersionPatch {
165-
errs = append(errs, validation.NewValueError(fmt.Sprintf("only Arazzo version %s and below is supported", Version), a.core, a.core.Arazzo))
156+
errs = append(errs, validation.NewValueError(fmt.Sprintf("only Arazzo version %s and below is supported", Version), a.GetCore(), a.GetCore().Arazzo))
166157
}
167158

168159
errs = append(errs, a.Info.Validate(ctx, opts...)...)
@@ -173,7 +164,7 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
173164
errs = append(errs, sourceDescription.Validate(ctx, opts...)...)
174165

175166
if _, ok := sourceDescriptionNames[sourceDescription.Name]; ok {
176-
errs = append(errs, validation.NewSliceError(fmt.Sprintf("sourceDescription name %s is not unique", sourceDescription.Name), a.core, a.core.SourceDescriptions, i))
167+
errs = append(errs, validation.NewSliceError(fmt.Sprintf("sourceDescription name %s is not unique", sourceDescription.Name), a.GetCore(), a.GetCore().SourceDescriptions, i))
177168
}
178169

179170
sourceDescriptionNames[sourceDescription.Name] = true
@@ -185,7 +176,7 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
185176
errs = append(errs, workflow.Validate(ctx, opts...)...)
186177

187178
if _, ok := workflowIds[workflow.WorkflowID]; ok {
188-
errs = append(errs, validation.NewSliceError(fmt.Sprintf("workflowId %s is not unique", workflow.WorkflowID), a.core, a.core.Workflows, i))
179+
errs = append(errs, validation.NewSliceError(fmt.Sprintf("workflowId %s is not unique", workflow.WorkflowID), a.GetCore(), a.GetCore().Workflows, i))
189180
}
190181

191182
workflowIds[workflow.WorkflowID] = true
@@ -195,9 +186,7 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
195186
errs = append(errs, a.Components.Validate(ctx, opts...)...)
196187
}
197188

198-
if len(errs) == 0 {
199-
a.Valid = true
200-
}
189+
a.Valid = len(errs) == 0 && a.GetCore().GetValid()
201190

202191
return errs
203192
}

arazzo/arazzo_test.go

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import (
1313
"testing"
1414

1515
"github.com/speakeasy-api/openapi/arazzo"
16+
"github.com/speakeasy-api/openapi/arazzo/core"
1617
"github.com/speakeasy-api/openapi/arazzo/criterion"
1718
"github.com/speakeasy-api/openapi/arazzo/expression"
1819
"github.com/speakeasy-api/openapi/extensions"
1920
"github.com/speakeasy-api/openapi/jsonpointer"
2021
"github.com/speakeasy-api/openapi/jsonschema/oas31"
22+
jsonschema_core "github.com/speakeasy-api/openapi/jsonschema/oas31/core"
23+
"github.com/speakeasy-api/openapi/marshaller"
2124
"github.com/speakeasy-api/openapi/pointer"
2225
"github.com/speakeasy-api/openapi/sequencedmap"
2326
"github.com/speakeasy-api/openapi/validation"
@@ -41,7 +44,9 @@ var testArazzoInstance = &arazzo.Arazzo{
4144
Line: 6,
4245
Column: 11,
4346
})),
44-
Valid: true,
47+
Model: marshaller.Model[core.Info]{
48+
Valid: true,
49+
},
4550
},
4651
SourceDescriptions: []*arazzo.SourceDescription{
4752
{
@@ -55,7 +60,9 @@ var testArazzoInstance = &arazzo.Arazzo{
5560
Line: 11,
5661
Column: 13,
5762
})),
58-
Valid: true,
63+
Model: marshaller.Model[core.SourceDescription]{
64+
Valid: true,
65+
},
5966
},
6067
},
6168
Workflows: []*arazzo.Workflow{
@@ -69,19 +76,27 @@ var testArazzoInstance = &arazzo.Arazzo{
6976
Name: "parameter1",
7077
In: pointer.From(arazzo.InQuery),
7178
Value: &yaml.Node{Value: "123", Kind: yaml.ScalarNode, Tag: "!!str", Line: 19, Column: 16, Style: yaml.DoubleQuotedStyle},
79+
Model: marshaller.Model[core.Parameter]{
80+
Valid: true,
81+
},
82+
},
83+
Model: marshaller.Model[core.Reusable[*core.Parameter]]{
7284
Valid: true,
7385
},
74-
Valid: true,
7586
},
7687
},
7788
Inputs: oas31.NewJSONSchemaFromSchema(&oas31.Schema{
7889
Type: oas31.NewTypeFromString("object"),
7990
Properties: sequencedmap.New(sequencedmap.NewElem("input1", oas31.NewJSONSchemaFromSchema(&oas31.Schema{
80-
Type: oas31.NewTypeFromString("string"),
81-
Valid: true,
91+
Type: oas31.NewTypeFromString("string"),
92+
Model: marshaller.Model[jsonschema_core.Schema]{
93+
Valid: true,
94+
},
8295
}))),
8396
Required: []string{"input1"},
84-
Valid: true,
97+
Model: marshaller.Model[jsonschema_core.Schema]{
98+
Valid: true,
99+
},
85100
}),
86101
Steps: []*arazzo.Step{
87102
{
@@ -92,7 +107,9 @@ var testArazzoInstance = &arazzo.Arazzo{
92107
{
93108
Reference: pointer.From[expression.Expression]("$components.parameters.userId"),
94109
Value: &yaml.Node{Value: "456", Kind: yaml.ScalarNode, Tag: "!!str", Style: yaml.DoubleQuotedStyle, Line: 33, Column: 20},
95-
Valid: true,
110+
Model: marshaller.Model[core.Reusable[*core.Parameter]]{
111+
Valid: true,
112+
},
96113
},
97114
},
98115
RequestBody: &arazzo.RequestBody{
@@ -133,46 +150,62 @@ var testArazzoInstance = &arazzo.Arazzo{
133150
{
134151
Target: jsonpointer.JSONPointer("/b"),
135152
Value: &yaml.Node{Value: "3", Kind: yaml.ScalarNode, Tag: "!!int", Line: 39, Column: 22},
136-
Valid: true,
153+
Model: marshaller.Model[core.PayloadReplacement]{
154+
Valid: true,
155+
},
137156
},
138157
},
139-
Valid: true,
158+
Model: marshaller.Model[core.RequestBody]{
159+
Valid: true,
160+
},
140161
},
141-
SuccessCriteria: []*criterion.Criterion{{Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{}, Valid: true}},
162+
SuccessCriteria: []*criterion.Criterion{{Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{}, Model: marshaller.Model[core.Criterion]{Valid: true}}},
142163
OnSuccess: []*arazzo.ReusableSuccessAction{
143164
{
144165
Reference: pointer.From[expression.Expression]("$components.successActions.success"),
145-
Valid: true,
166+
Model: marshaller.Model[core.Reusable[*core.SuccessAction]]{
167+
Valid: true,
168+
},
146169
},
147170
},
148171
OnFailure: []*arazzo.ReusableFailureAction{
149172
{
150173
Reference: pointer.From[expression.Expression]("$components.failureActions.failure"),
151-
Valid: true,
174+
Model: marshaller.Model[core.Reusable[*core.FailureAction]]{
175+
Valid: true,
176+
},
152177
},
153178
},
154179
Outputs: sequencedmap.New(sequencedmap.NewElem[string, expression.Expression]("name", "$response.body#/name")),
155-
Valid: true,
180+
Model: marshaller.Model[core.Step]{
181+
Valid: true,
182+
},
156183
},
157184
},
158185
Outputs: sequencedmap.New(sequencedmap.NewElem[string, expression.Expression]("name", "$steps.step1.outputs.name")),
159-
Valid: true,
186+
Model: marshaller.Model[core.Workflow]{
187+
Valid: true,
188+
},
160189
},
161190
},
162191
Components: &arazzo.Components{
163192
Parameters: sequencedmap.New(sequencedmap.NewElem("userId", &arazzo.Parameter{
164193
Name: "userId",
165194
In: pointer.From(arazzo.InQuery),
166195
Value: &yaml.Node{Value: "123", Kind: yaml.ScalarNode, Tag: "!!str"},
167-
Valid: true,
196+
Model: marshaller.Model[core.Parameter]{
197+
Valid: true,
198+
},
168199
})),
169200
SuccessActions: sequencedmap.New(sequencedmap.NewElem("success", &arazzo.SuccessAction{
170201
Name: "success",
171202
Type: arazzo.SuccessActionTypeEnd,
172203
Criteria: []criterion.Criterion{{Context: pointer.From(expression.Expression("$statusCode")), Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{
173204
Type: pointer.From(criterion.CriterionTypeSimple),
174205
}}},
175-
Valid: true,
206+
Model: marshaller.Model[core.SuccessAction]{
207+
Valid: true,
208+
},
176209
})),
177210
FailureActions: sequencedmap.New(sequencedmap.NewElem("failure", &arazzo.FailureAction{
178211
Name: "failure",
@@ -182,9 +215,13 @@ var testArazzoInstance = &arazzo.Arazzo{
182215
Criteria: []criterion.Criterion{{Context: pointer.From(expression.Expression("$statusCode")), Condition: "$statusCode == 500", Type: criterion.CriterionTypeUnion{
183216
Type: pointer.From(criterion.CriterionTypeSimple),
184217
}}},
185-
Valid: true,
218+
Model: marshaller.Model[core.FailureAction]{
219+
Valid: true,
220+
},
186221
})),
187-
Valid: true,
222+
Model: marshaller.Model[core.Components]{
223+
Valid: true,
224+
},
188225
},
189226
Extensions: extensions.New(extensions.NewElem("x-test", &yaml.Node{
190227
Value: "some-value",
@@ -193,7 +230,9 @@ var testArazzoInstance = &arazzo.Arazzo{
193230
Line: 72,
194231
Column: 9,
195232
})),
196-
Valid: true,
233+
Model: marshaller.Model[core.Arazzo]{
234+
Valid: true,
235+
},
197236
}
198237

199238
func TestArazzo_Unmarshal_Success(t *testing.T) {
@@ -265,7 +304,9 @@ sourceDescriptions:
265304
Info: arazzo.Info{
266305
Title: "My Workflow",
267306
Version: "",
268-
Valid: true,
307+
Model: marshaller.Model[core.Info]{
308+
Valid: false,
309+
},
269310
},
270311
SourceDescriptions: []*arazzo.SourceDescription{
271312
{

arazzo/components.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import (
99
"github.com/speakeasy-api/openapi/extensions"
1010
"github.com/speakeasy-api/openapi/internal/interfaces"
1111
"github.com/speakeasy-api/openapi/jsonschema/oas31"
12+
"github.com/speakeasy-api/openapi/marshaller"
1213
"github.com/speakeasy-api/openapi/sequencedmap"
1314
"github.com/speakeasy-api/openapi/validation"
1415
)
1516

1617
// Components holds reusable components that can be referenced in an Arazzo document.
1718
type Components struct {
19+
marshaller.Model[core.Components]
20+
1821
// Inputs provides a list of reusable JSON Schemas that can be referenced from inputs and other JSON Schemas.
1922
Inputs *sequencedmap.Map[string, oas31.JSONSchema]
2023
// Parameters provides a list of reusable parameters that can be referenced from workflows and steps.
@@ -25,21 +28,10 @@ type Components struct {
2528
FailureActions *sequencedmap.Map[string, *FailureAction]
2629
// Extensions provides a list of extensions to the Components object.
2730
Extensions *extensions.Extensions
28-
29-
// Valid indicates whether this model passed validation.
30-
Valid bool
31-
32-
core core.Components
3331
}
3432

3533
var _ interfaces.Model[core.Components] = (*Components)(nil)
3634

37-
// GetCore will return the low level representation of the components object.
38-
// Useful for accessing line and column numbers for various nodes in the backing yaml/json document.
39-
func (c *Components) GetCore() *core.Components {
40-
return &c.core
41-
}
42-
4335
var componentNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\.\-_]+$`)
4436

4537
type componentKey struct {
@@ -49,10 +41,11 @@ type componentKey struct {
4941
// Validate validates the Components object.
5042
func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []error {
5143
errs := []error{}
44+
core := c.GetCore()
5245

5346
for key, input := range c.Inputs.All() {
5447
if !componentNameRegex.MatchString(key) {
55-
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("input key must be a valid key [%s]: %s", componentNameRegex.String(), key), c.core, c.core.Inputs, key))
48+
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("input key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.Inputs, key))
5649
}
5750

5851
if input.IsLeft() {
@@ -64,7 +57,7 @@ func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []
6457

6558
for key, parameter := range c.Parameters.All() {
6659
if !componentNameRegex.MatchString(key) {
67-
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("parameter key must be a valid key [%s]: %s", componentNameRegex.String(), key), c.core, c.core.Parameters, key))
60+
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("parameter key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.Parameters, key))
6861
}
6962

7063
paramOps := append(opts, validation.WithContextObject(&componentKey{name: key}))
@@ -74,7 +67,7 @@ func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []
7467

7568
for key, successAction := range c.SuccessActions.All() {
7669
if !componentNameRegex.MatchString(key) {
77-
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("successAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), c.core, c.core.SuccessActions, key))
70+
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("successAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.SuccessActions, key))
7871
}
7972

8073
successActionOps := append(opts, validation.WithContextObject(&componentKey{name: key}))
@@ -84,17 +77,15 @@ func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []
8477

8578
for key, failureAction := range c.FailureActions.All() {
8679
if !componentNameRegex.MatchString(key) {
87-
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("failureAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), c.core, c.core.FailureActions, key))
80+
errs = append(errs, validation.NewMapKeyError(fmt.Sprintf("failureAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.FailureActions, key))
8881
}
8982

9083
failureActionOps := append(opts, validation.WithContextObject(&componentKey{name: key}))
9184

9285
errs = append(errs, failureAction.Validate(ctx, failureActionOps...)...)
9386
}
9487

95-
if len(errs) == 0 {
96-
c.Valid = true
97-
}
88+
c.Valid = len(errs) == 0 && core.GetValid()
9889

9990
return errs
10091
}

arazzo/core/arazzo.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ import (
1414
)
1515

1616
type Arazzo struct {
17+
marshaller.CoreModel
18+
1719
Arazzo marshaller.Node[string] `key:"arazzo"`
1820
Info marshaller.Node[Info] `key:"info"`
1921
SourceDescriptions marshaller.Node[[]*SourceDescription] `key:"sourceDescriptions" required:"true"`
2022
Workflows marshaller.Node[[]*Workflow] `key:"workflows" required:"true"`
2123
Components marshaller.Node[*Components] `key:"components"`
2224
Extensions core.Extensions `key:"extensions"`
2325

24-
RootNode *yaml.Node
25-
Config *yml.Config
26+
Config *yml.Config
2627
}
2728

2829
func Unmarshal(ctx context.Context, doc io.Reader) (*Arazzo, error) {

0 commit comments

Comments
 (0)