-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharazzo.go
202 lines (162 loc) · 6.23 KB
/
arazzo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Package arazzo provides an API for working with Arazzo documents including reading, creating, mutating, walking and validating them.
//
// The Arazzo Specification is a mechanism for orchestrating API calls, defining their sequences and dependencies, to achieve specific outcomes when working with API descriptions like OpenAPI.
package arazzo
import (
"context"
"errors"
"fmt"
"io"
"slices"
"github.com/speakeasy-api/openapi/arazzo/core"
"github.com/speakeasy-api/openapi/extensions"
"github.com/speakeasy-api/openapi/marshaller"
"github.com/speakeasy-api/openapi/validation"
"github.com/speakeasy-api/openapi/yml"
)
// Version is the version of the Arazzo Specification that this package conforms to.
const Version = "1.0.0"
// Arazzo is the root object for an Arazzo document.
type Arazzo struct {
// Arazzo is the version of the Arazzo Specification that this document conforms to.
Arazzo string
// Info provides metadata about the Arazzo document.
Info Info
// SourceDescriptions provides a list of SourceDescription objects that describe the source of the data that the workflow is orchestrating.
SourceDescriptions SourceDescriptions
// Workflows provides a list of Workflow objects that describe the orchestration of API calls.
Workflows Workflows
// Components provides a list of reusable components that can be used in the workflow.
Components *Components
// Extensions provides a list of extensions to the Arazzo document.
Extensions *extensions.Extensions
// Valid indicates whether this model passed validation.
Valid bool
core core.Arazzo
}
var _ model[core.Arazzo] = (*Arazzo)(nil)
type Option[T any] func(o *T)
type unmarshalOptions struct {
skipValidation bool
}
// WithSkipValidation will skip validation of the Arazzo document during unmarshalling.
// Useful to quickly load a document that will be mutated and validated later.
func WithSkipValidation() Option[unmarshalOptions] {
return func(o *unmarshalOptions) {
o.skipValidation = true
}
}
// Unmarshal will unmarshal and validate an Arazzo document from the provided io.Reader.
// Validation can be skipped by using arazzo.WithSkipValidation() as one of the options when calling this function.
func Unmarshal(ctx context.Context, doc io.Reader, opts ...Option[unmarshalOptions]) (*Arazzo, []error, error) {
o := unmarshalOptions{}
for _, opt := range opts {
opt(&o)
}
ctx = validation.ContextWithValidationContext(ctx)
c, err := core.Unmarshal(ctx, doc)
if err != nil {
return nil, nil, err
}
arazzo := &Arazzo{}
if err := marshaller.PopulateModel(*c, arazzo); err != nil {
return nil, nil, err
}
var validationErrs []error
if !o.skipValidation {
validationErrs = validation.GetValidationErrors(ctx)
validationErrs = append(validationErrs, arazzo.Validate(ctx)...)
slices.SortFunc(validationErrs, func(a, b error) int {
var aValidationErr *validation.Error
var bValidationErr *validation.Error
aIsValidationErr := errors.As(a, &aValidationErr)
bIsValidationErr := errors.As(b, &bValidationErr)
if aIsValidationErr && bIsValidationErr {
if aValidationErr.Line == bValidationErr.Line {
return aValidationErr.Column - bValidationErr.Column
}
return aValidationErr.Line - bValidationErr.Line
} else if aIsValidationErr {
return -1
} else if bIsValidationErr {
return 1
}
return 0
})
}
return arazzo, validationErrs, nil
}
// Marshal will marshal the provided Arazzo document to the provided io.Writer.
func Marshal(ctx context.Context, arazzo *Arazzo, w io.Writer) error {
if arazzo == nil {
return errors.New("nil *Arazzo")
}
if err := arazzo.Marshal(ctx, w); err != nil {
return err
}
return nil
}
// GetCore will return the low level representation of the Arazzo document.
// Useful for accessing line and column numbers for various nodes in the backing yaml/json document.
func (a *Arazzo) GetCore() *core.Arazzo {
return &a.core
}
// Sync will sync any changes made to the Arazzo document models back to the core models.
func (a *Arazzo) Sync(ctx context.Context) error {
if _, err := marshaller.SyncValue(ctx, a, &a.core, nil, false); err != nil {
return err
}
return nil
}
// Marshal will marshal the Arazzo document to the provided io.Writer.
func (a *Arazzo) Marshal(ctx context.Context, w io.Writer) error {
ctx = yml.ContextWithConfig(ctx, a.core.Config)
if _, err := marshaller.SyncValue(ctx, a, &a.core, nil, false); err != nil {
return err
}
return a.core.Marshal(ctx, w)
}
// Validate will validate the Arazzo document against the Arazzo Specification.
func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []error {
opts = append(opts, validation.WithContextObject(a))
errs := []error{}
if a.Arazzo != Version {
errs = append(errs, &validation.Error{
Message: "Arazzo version must be 1.0.0",
Line: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Line,
Column: a.core.Arazzo.GetValueNodeOrRoot(a.core.RootNode).Column,
})
}
errs = append(errs, a.Info.Validate(ctx, opts...)...)
sourceDescriptionNames := make(map[string]bool)
for i, sourceDescription := range a.SourceDescriptions {
errs = append(errs, sourceDescription.Validate(ctx, opts...)...)
if _, ok := sourceDescriptionNames[sourceDescription.Name]; ok {
errs = append(errs, &validation.Error{
Message: fmt.Sprintf("sourceDescription name %s is not unique", sourceDescription.Name),
Line: a.core.SourceDescriptions.GetSliceValueNodeOrRoot(i, a.core.RootNode).Line,
Column: a.core.SourceDescriptions.GetSliceValueNodeOrRoot(i, a.core.RootNode).Column,
})
}
sourceDescriptionNames[sourceDescription.Name] = true
}
workflowIds := make(map[string]bool)
for i, workflow := range a.Workflows {
errs = append(errs, workflow.Validate(ctx, opts...)...)
if _, ok := workflowIds[workflow.WorkflowID]; ok {
errs = append(errs, &validation.Error{
Message: fmt.Sprintf("workflowId %s is not unique", workflow.WorkflowID),
Line: a.core.Workflows.GetSliceValueNodeOrRoot(i, a.core.RootNode).Line,
Column: a.core.Workflows.GetSliceValueNodeOrRoot(i, a.core.RootNode).Column,
})
}
workflowIds[workflow.WorkflowID] = true
}
if a.Components != nil {
errs = append(errs, a.Components.Validate(ctx, opts...)...)
}
if len(errs) == 0 {
a.Valid = true
}
return errs
}