Skip to content

Commit aff7de4

Browse files
refactor: improve unmarshalling performance and memory usage
1 parent c8ef45f commit aff7de4

File tree

137 files changed

+9717
-5434
lines changed

Some content is hidden

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

137 files changed

+9717
-5434
lines changed

.gitignore

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

arazzo/arazzo.go

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/speakeasy-api/openapi/internal/utils"
1717
"github.com/speakeasy-api/openapi/marshaller"
1818
"github.com/speakeasy-api/openapi/validation"
19-
"github.com/speakeasy-api/openapi/yml"
2019
)
2120

2221
// Version is the version of the Arazzo Specification that this package conforms to.
@@ -69,88 +68,66 @@ func Unmarshal(ctx context.Context, doc io.Reader, opts ...Option[unmarshalOptio
6968
opt(&o)
7069
}
7170

72-
c, err := core.Unmarshal(ctx, doc)
71+
var arazzo Arazzo
72+
validationErrs, err := marshaller.Unmarshal(ctx, doc, &arazzo)
7373
if err != nil {
74-
return nil, nil, err
74+
return nil, nil, fmt.Errorf("failed to unmarshal Arazzo document: %w", err)
7575
}
7676

77-
arazzo := &Arazzo{}
78-
if err := marshaller.Populate(*c, arazzo); err != nil {
79-
return nil, nil, err
77+
if o.skipValidation {
78+
return &arazzo, nil, nil
8079
}
8180

82-
var validationErrs []error
83-
if !o.skipValidation {
84-
validationErrs = append(validationErrs, arazzo.Validate(ctx)...)
85-
slices.SortFunc(validationErrs, func(a, b error) int {
86-
var aValidationErr *validation.Error
87-
var bValidationErr *validation.Error
88-
aIsValidationErr := errors.As(a, &aValidationErr)
89-
bIsValidationErr := errors.As(b, &bValidationErr)
90-
if aIsValidationErr && bIsValidationErr {
91-
if aValidationErr.Line == bValidationErr.Line {
92-
return aValidationErr.Column - bValidationErr.Column
93-
}
94-
return aValidationErr.Line - bValidationErr.Line
95-
} else if aIsValidationErr {
96-
return -1
97-
} else if bIsValidationErr {
98-
return 1
81+
validationErrs = append(validationErrs, arazzo.Validate(ctx)...)
82+
slices.SortFunc(validationErrs, func(a, b error) int {
83+
var aValidationErr *validation.Error
84+
var bValidationErr *validation.Error
85+
aIsValidationErr := errors.As(a, &aValidationErr)
86+
bIsValidationErr := errors.As(b, &bValidationErr)
87+
if aIsValidationErr && bIsValidationErr {
88+
if aValidationErr.Line == bValidationErr.Line {
89+
return aValidationErr.Column - bValidationErr.Column
9990
}
91+
return aValidationErr.Line - bValidationErr.Line
92+
} else if aIsValidationErr {
93+
return -1
94+
} else if bIsValidationErr {
95+
return 1
96+
}
10097

101-
return 0
102-
})
103-
}
98+
return 0
99+
})
104100

105-
return arazzo, validationErrs, nil
101+
return &arazzo, validationErrs, nil
106102
}
107103

108104
// Marshal will marshal the provided Arazzo document to the provided io.Writer.
109105
func Marshal(ctx context.Context, arazzo *Arazzo, w io.Writer) error {
110-
if arazzo == nil {
111-
return errors.New("nil *Arazzo")
112-
}
113-
114-
if err := arazzo.Marshal(ctx, w); err != nil {
115-
return err
116-
}
117-
118-
return nil
106+
return marshaller.Marshal(ctx, arazzo, w)
119107
}
120108

121109
// Sync will sync any changes made to the Arazzo document models back to the core models.
122110
func (a *Arazzo) Sync(ctx context.Context) error {
123-
if _, err := marshaller.SyncValue(ctx, a, a.GetCore(), nil, false); err != nil {
111+
if _, err := marshaller.SyncValue(ctx, a, a.GetCore(), a.GetRootNode(), false); err != nil {
124112
return err
125113
}
126114
return nil
127115
}
128116

129-
// Marshal will marshal the Arazzo document to the provided io.Writer.
130-
func (a *Arazzo) Marshal(ctx context.Context, w io.Writer) error {
131-
ctx = yml.ContextWithConfig(ctx, a.GetCore().Config)
132-
133-
if _, err := marshaller.SyncValue(ctx, a, a.GetCore(), nil, false); err != nil {
134-
return err
135-
}
136-
137-
return a.GetCore().Marshal(ctx, w)
138-
}
139-
140117
// Validate will validate the Arazzo document against the Arazzo Specification.
141118
func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []error {
142119
opts = append(opts, validation.WithContextObject(a))
143120

144121
core := a.GetCore()
145-
errs := core.GetValidationErrors()
122+
errs := []error{}
146123

147124
arazzoMajor, arazzoMinor, arazzoPatch, err := utils.ParseVersion(a.Arazzo)
148125
if err != nil {
149-
errs = append(errs, validation.NewValueError(fmt.Sprintf("invalid Arazzo version in document %s: %s", a.Arazzo, err.Error()), core, core.Arazzo))
126+
errs = append(errs, validation.NewValueError(validation.NewValueValidationError("invalid Arazzo version in document %s: %s", a.Arazzo, err.Error()), core, core.Arazzo))
150127
}
151128

152129
if arazzoMajor != VersionMajor || arazzoMinor != VersionMinor || arazzoPatch > VersionPatch {
153-
errs = append(errs, validation.NewValueError(fmt.Sprintf("only Arazzo version %s and below is supported", Version), core, core.Arazzo))
130+
errs = append(errs, validation.NewValueError(validation.NewValueValidationError("only Arazzo version %s and below is supported", Version), core, core.Arazzo))
154131
}
155132

156133
errs = append(errs, a.Info.Validate(ctx, opts...)...)
@@ -161,7 +138,7 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
161138
errs = append(errs, sourceDescription.Validate(ctx, opts...)...)
162139

163140
if _, ok := sourceDescriptionNames[sourceDescription.Name]; ok {
164-
errs = append(errs, validation.NewSliceError(fmt.Sprintf("sourceDescription name %s is not unique", sourceDescription.Name), core, core.SourceDescriptions, i))
141+
errs = append(errs, validation.NewSliceError(validation.NewValueValidationError("sourceDescription name %s is not unique", sourceDescription.Name), core, core.SourceDescriptions, i))
165142
}
166143

167144
sourceDescriptionNames[sourceDescription.Name] = true
@@ -173,7 +150,7 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro
173150
errs = append(errs, workflow.Validate(ctx, opts...)...)
174151

175152
if _, ok := workflowIds[workflow.WorkflowID]; ok {
176-
errs = append(errs, validation.NewSliceError(fmt.Sprintf("workflowId %s is not unique", workflow.WorkflowID), core, core.Workflows, i))
153+
errs = append(errs, validation.NewSliceError(validation.NewValueValidationError("workflowId %s is not unique", workflow.WorkflowID), core, core.Workflows, i))
177154
}
178155

179156
workflowIds[workflow.WorkflowID] = true

arazzo/arazzo_examples_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/speakeasy-api/openapi/pointer"
1111
)
1212

13-
// The below examples should be copied into the README.md file if every changed TODO: automate this
13+
// The below examples should be copied into the README.md file if ever changed TODO: automate this
1414
func Example_readAndMutate() {
1515
ctx := context.Background()
1616

@@ -48,7 +48,7 @@ func Example_readAndMutate() {
4848
func Example_creating() {
4949
ctx := context.Background()
5050

51-
arazzo := &arazzo.Arazzo{
51+
a := &arazzo.Arazzo{
5252
Arazzo: arazzo.Version,
5353
Info: arazzo.Info{
5454
Title: "My Workflow",
@@ -60,7 +60,7 @@ func Example_creating() {
6060

6161
buf := bytes.NewBuffer([]byte{})
6262

63-
err := arazzo.Marshal(ctx, buf)
63+
err := arazzo.Marshal(ctx, a, buf)
6464
if err != nil {
6565
panic(err)
6666
}
@@ -76,16 +76,16 @@ func Example_mutating() {
7676
panic(err)
7777
}
7878

79-
arazzo, _, err := arazzo.Unmarshal(ctx, f)
79+
a, _, err := arazzo.Unmarshal(ctx, f)
8080
if err != nil {
8181
panic(err)
8282
}
8383

84-
arazzo.Info.Title = "My updated workflow title"
84+
a.Info.Title = "My updated workflow title"
8585

8686
buf := bytes.NewBuffer([]byte{})
8787

88-
if err := arazzo.Marshal(ctx, buf); err != nil {
88+
if err := arazzo.Marshal(ctx, a, buf); err != nil {
8989
panic(err)
9090
}
9191

arazzo/arazzo_order_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"testing"
99

1010
"github.com/speakeasy-api/openapi/arazzo"
11-
"github.com/speakeasy-api/openapi/arazzo/expression"
11+
"github.com/speakeasy-api/openapi/expression"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
1414
"gopkg.in/yaml.v3"

arazzo/arazzo_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"github.com/speakeasy-api/openapi/arazzo"
1616
"github.com/speakeasy-api/openapi/arazzo/core"
1717
"github.com/speakeasy-api/openapi/arazzo/criterion"
18-
"github.com/speakeasy-api/openapi/arazzo/expression"
18+
"github.com/speakeasy-api/openapi/expression"
1919
"github.com/speakeasy-api/openapi/extensions"
2020
"github.com/speakeasy-api/openapi/jsonpointer"
2121
"github.com/speakeasy-api/openapi/jsonschema/oas31"
@@ -292,11 +292,11 @@ sourceDescriptions:
292292
require.NoError(t, err)
293293

294294
assert.Equal(t, []error{
295-
&validation.Error{Line: 1, Column: 1, Message: "field workflows is missing"},
296-
&validation.Error{Line: 1, Column: 9, Message: "only Arazzo version 1.0.1 and below is supported"},
297-
&validation.Error{Line: 4, Column: 3, Message: "field version is missing"},
298-
&validation.Error{Line: 6, Column: 5, Message: "field url is missing"},
299-
&validation.Error{Line: 7, Column: 11, Message: "type must be one of [openapi, arazzo]"},
295+
&validation.Error{Line: 1, Column: 1, UnderlyingError: validation.NewMissingFieldError("field workflows is missing")},
296+
&validation.Error{Line: 1, Column: 9, UnderlyingError: validation.NewValueValidationError("only Arazzo version 1.0.1 and below is supported")},
297+
&validation.Error{Line: 4, Column: 3, UnderlyingError: validation.NewMissingFieldError("field version is missing")},
298+
&validation.Error{Line: 6, Column: 5, UnderlyingError: validation.NewMissingFieldError("field url is missing")},
299+
&validation.Error{Line: 7, Column: 11, UnderlyingError: validation.NewValueValidationError("type must be one of [openapi, arazzo]")},
300300
}, validationErrs)
301301

302302
expected := &arazzo.Arazzo{

arazzo/components.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package arazzo
22

33
import (
44
"context"
5-
"fmt"
65
"regexp"
76

87
"github.com/speakeasy-api/openapi/arazzo/core"
@@ -41,11 +40,11 @@ type componentKey struct {
4140
// Validate validates the Components object.
4241
func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []error {
4342
core := c.GetCore()
44-
errs := core.GetValidationErrors()
43+
errs := []error{}
4544

4645
for key, input := range c.Inputs.All() {
4746
if !componentNameRegex.MatchString(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))
47+
errs = append(errs, validation.NewMapKeyError(validation.NewValueValidationError("input key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.Inputs, key))
4948
}
5049

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

5857
for key, parameter := range c.Parameters.All() {
5958
if !componentNameRegex.MatchString(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))
59+
errs = append(errs, validation.NewMapKeyError(validation.NewValueValidationError("parameter key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.Parameters, key))
6160
}
6261

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

6867
for key, successAction := range c.SuccessActions.All() {
6968
if !componentNameRegex.MatchString(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))
69+
errs = append(errs, validation.NewMapKeyError(validation.NewValueValidationError("successAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.SuccessActions, key))
7170
}
7271

7372
successActionOps := append(opts, validation.WithContextObject(&componentKey{name: key}))
@@ -77,7 +76,7 @@ func (c *Components) Validate(ctx context.Context, opts ...validation.Option) []
7776

7877
for key, failureAction := range c.FailureActions.All() {
7978
if !componentNameRegex.MatchString(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))
79+
errs = append(errs, validation.NewMapKeyError(validation.NewValueValidationError("failureAction key must be a valid key [%s]: %s", componentNameRegex.String(), key), core, core.FailureActions, key))
8180
}
8281

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

arazzo/core/arazzo.go

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
package core
22

33
import (
4-
"context"
5-
"errors"
6-
"fmt"
7-
"io"
8-
94
"github.com/speakeasy-api/openapi/extensions/core"
10-
"github.com/speakeasy-api/openapi/json"
115
"github.com/speakeasy-api/openapi/marshaller"
12-
"github.com/speakeasy-api/openapi/yml"
13-
"gopkg.in/yaml.v3"
146
)
157

168
type Arazzo struct {
@@ -22,51 +14,4 @@ type Arazzo struct {
2214
Workflows marshaller.Node[[]*Workflow] `key:"workflows" required:"true"`
2315
Components marshaller.Node[*Components] `key:"components"`
2416
Extensions core.Extensions `key:"extensions"`
25-
26-
Config *yml.Config
27-
}
28-
29-
func Unmarshal(ctx context.Context, doc io.Reader) (*Arazzo, error) {
30-
data, err := io.ReadAll(doc)
31-
if err != nil {
32-
return nil, fmt.Errorf("failed to read Arazzo document: %w", err)
33-
}
34-
35-
if len(data) == 0 {
36-
return nil, errors.New("empty document")
37-
}
38-
39-
var root yaml.Node
40-
if err := yaml.Unmarshal(data, &root); err != nil {
41-
return nil, fmt.Errorf("failed to unmarshal Arazzo document: %w", err)
42-
}
43-
44-
var arazzo Arazzo
45-
if err := marshaller.Unmarshal(ctx, &root, &arazzo); err != nil {
46-
return nil, err
47-
}
48-
49-
arazzo.Config = yml.GetConfigFromDoc(data, &root)
50-
51-
return &arazzo, nil
52-
}
53-
54-
func (a *Arazzo) Marshal(ctx context.Context, w io.Writer) error {
55-
cfg := yml.GetConfigFromContext(ctx)
56-
57-
switch cfg.OutputFormat {
58-
case yml.OutputFormatYAML:
59-
enc := yaml.NewEncoder(w)
60-
61-
enc.SetIndent(cfg.Indentation)
62-
if err := enc.Encode(a.RootNode); err != nil {
63-
return err
64-
}
65-
case yml.OutputFormatJSON:
66-
if err := json.YAMLToJSON(a.RootNode, cfg.Indentation, w); err != nil {
67-
return err
68-
}
69-
}
70-
71-
return nil
7217
}

0 commit comments

Comments
 (0)