diff --git a/arazzo/arazzo.go b/arazzo/arazzo.go index 9ea73e5..0672b69 100644 --- a/arazzo/arazzo.go +++ b/arazzo/arazzo.go @@ -1,4 +1,4 @@ -// package arazzo provides an API for working with Arazzo documents including reading, creating, mutating, walking and validating them. +// 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 @@ -35,6 +35,9 @@ type Arazzo struct { // 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 } @@ -183,5 +186,9 @@ func (a *Arazzo) Validate(ctx context.Context, opts ...validation.Option) []erro errs = append(errs, a.Components.Validate(ctx, opts...)...) } + if len(errs) == 0 { + a.Valid = true + } + return errs } diff --git a/arazzo/arazzo_test.go b/arazzo/arazzo_test.go index 1171c6d..3e3c134 100644 --- a/arazzo/arazzo_test.go +++ b/arazzo/arazzo_test.go @@ -41,8 +41,9 @@ var testArazzoInstance = &arazzo.Arazzo{ Line: 6, Column: 11, })), + Valid: true, }, - SourceDescriptions: []arazzo.SourceDescription{ + SourceDescriptions: []*arazzo.SourceDescription{ { Name: "openapi", URL: "https://openapi.com", @@ -54,38 +55,44 @@ var testArazzoInstance = &arazzo.Arazzo{ Line: 11, Column: 13, })), + Valid: true, }, }, - Workflows: []arazzo.Workflow{ + Workflows: []*arazzo.Workflow{ { WorkflowID: "workflow1", Summary: pointer.From("A summary"), Description: pointer.From("A description"), - Parameters: []arazzo.ReusableParameter{ + Parameters: []*arazzo.ReusableParameter{ { Object: &arazzo.Parameter{ Name: "parameter1", In: pointer.From(arazzo.InQuery), Value: &yaml.Node{Value: "123", Kind: yaml.ScalarNode, Tag: "!!str", Line: 19, Column: 16, Style: yaml.DoubleQuotedStyle}, + Valid: true, }, + Valid: true, }, }, Inputs: oas31.NewJSONSchemaFromSchema(&oas31.Schema{ Type: oas31.NewTypeFromString("object"), Properties: sequencedmap.New(sequencedmap.NewElem("input1", oas31.NewJSONSchemaFromSchema(&oas31.Schema{ - Type: oas31.NewTypeFromString("string"), + Type: oas31.NewTypeFromString("string"), + Valid: true, }))), Required: []string{"input1"}, + Valid: true, }), - Steps: []arazzo.Step{ + Steps: []*arazzo.Step{ { StepID: "step1", Description: pointer.From("A description"), OperationID: pointer.From[expression.Expression]("operation1"), - Parameters: []arazzo.ReusableParameter{ + Parameters: []*arazzo.ReusableParameter{ { Reference: pointer.From[expression.Expression]("$components.parameters.userId"), Value: &yaml.Node{Value: "456", Kind: yaml.ScalarNode, Tag: "!!str", Style: yaml.DoubleQuotedStyle, Line: 33, Column: 20}, + Valid: true, }, }, RequestBody: &arazzo.RequestBody{ @@ -122,44 +129,52 @@ var testArazzoInstance = &arazzo.Arazzo{ Column: 34, }, }, Kind: yaml.MappingNode, Tag: "!!map", Style: yaml.FlowStyle, Line: 36, Column: 20}, - Replacements: []arazzo.PayloadReplacement{ + Replacements: []*arazzo.PayloadReplacement{ { Target: jsonpointer.JSONPointer("/b"), Value: &yaml.Node{Value: "3", Kind: yaml.ScalarNode, Tag: "!!int", Line: 39, Column: 22}, + Valid: true, }, }, + Valid: true, }, - SuccessCriteria: []criterion.Criterion{{Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{}}}, - OnSuccess: []arazzo.ReusableSuccessAction{ + SuccessCriteria: []*criterion.Criterion{{Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{}, Valid: true}}, + OnSuccess: []*arazzo.ReusableSuccessAction{ { Reference: pointer.From[expression.Expression]("$components.successActions.success"), + Valid: true, }, }, - OnFailure: []arazzo.ReusableFailureAction{ + OnFailure: []*arazzo.ReusableFailureAction{ { Reference: pointer.From[expression.Expression]("$components.failureActions.failure"), + Valid: true, }, }, Outputs: sequencedmap.New(sequencedmap.NewElem[string, expression.Expression]("name", "$response.body#/name")), + Valid: true, }, }, Outputs: sequencedmap.New(sequencedmap.NewElem[string, expression.Expression]("name", "$steps.step1.outputs.name")), + Valid: true, }, }, Components: &arazzo.Components{ - Parameters: sequencedmap.New(sequencedmap.NewElem("userId", arazzo.Parameter{ + Parameters: sequencedmap.New(sequencedmap.NewElem("userId", &arazzo.Parameter{ Name: "userId", In: pointer.From(arazzo.InQuery), Value: &yaml.Node{Value: "123", Kind: yaml.ScalarNode, Tag: "!!str"}, + Valid: true, })), - SuccessActions: sequencedmap.New(sequencedmap.NewElem("success", arazzo.SuccessAction{ + SuccessActions: sequencedmap.New(sequencedmap.NewElem("success", &arazzo.SuccessAction{ Name: "success", Type: arazzo.SuccessActionTypeEnd, Criteria: []criterion.Criterion{{Context: pointer.From(expression.Expression("$statusCode")), Condition: "$statusCode == 200", Type: criterion.CriterionTypeUnion{ Type: pointer.From(criterion.CriterionTypeSimple), }}}, + Valid: true, })), - FailureActions: sequencedmap.New(sequencedmap.NewElem("failure", arazzo.FailureAction{ + FailureActions: sequencedmap.New(sequencedmap.NewElem("failure", &arazzo.FailureAction{ Name: "failure", Type: arazzo.FailureActionTypeRetry, RetryAfter: pointer.From(10.0), @@ -167,7 +182,9 @@ var testArazzoInstance = &arazzo.Arazzo{ Criteria: []criterion.Criterion{{Condition: "$statusCode == 500", Type: criterion.CriterionTypeUnion{ Type: pointer.From(criterion.CriterionTypeSimple), }}}, + Valid: true, })), + Valid: true, }, Extensions: extensions.New(extensions.NewElem("x-test", &yaml.Node{ Value: "some-value", @@ -176,6 +193,7 @@ var testArazzoInstance = &arazzo.Arazzo{ Line: 72, Column: 9, })), + Valid: true, } func TestArazzo_Unmarshal_Success(t *testing.T) { @@ -247,8 +265,9 @@ sourceDescriptions: Info: arazzo.Info{ Title: "My Workflow", Version: "", + Valid: true, }, - SourceDescriptions: []arazzo.SourceDescription{ + SourceDescriptions: []*arazzo.SourceDescription{ { Name: "openapi", Type: "openapis", @@ -406,7 +425,7 @@ workflows: [] a.Extensions = extensions.New() a.Info.Summary = nil a.Info.Extensions = extensions.New() - a.SourceDescriptions = []arazzo.SourceDescription{} + a.SourceDescriptions = []*arazzo.SourceDescription{} outBuf := bytes.NewBuffer([]byte{}) diff --git a/arazzo/components.go b/arazzo/components.go index de724fd..a35ce19 100644 --- a/arazzo/components.go +++ b/arazzo/components.go @@ -17,14 +17,17 @@ type Components struct { // Inputs provides a list of reusable JSON Schemas that can be referenced from inputs and other JSON Schemas. Inputs *sequencedmap.Map[string, oas31.JSONSchema] // Parameters provides a list of reusable parameters that can be referenced from workflows and steps. - Parameters *sequencedmap.Map[string, Parameter] + Parameters *sequencedmap.Map[string, *Parameter] // SuccessActions provides a list of reusable success actions that can be referenced from workflows and steps. - SuccessActions *sequencedmap.Map[string, SuccessAction] + SuccessActions *sequencedmap.Map[string, *SuccessAction] // FailureActions provides a list of reusable failure actions that can be referenced from workflows and steps. - FailureActions *sequencedmap.Map[string, FailureAction] + FailureActions *sequencedmap.Map[string, *FailureAction] // Extensions provides a list of extensions to the Components object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Components } @@ -104,5 +107,9 @@ func (c *Components) Validate(ctx context.Context, opts ...validation.Option) [] errs = append(errs, failureAction.Validate(ctx, failureActionOps...)...) } + if len(errs) == 0 { + c.Valid = true + } + return errs } diff --git a/arazzo/core/arazzo.go b/arazzo/core/arazzo.go index 79c3589..8c97668 100644 --- a/arazzo/core/arazzo.go +++ b/arazzo/core/arazzo.go @@ -13,12 +13,12 @@ import ( ) type Arazzo struct { - Arazzo marshaller.Node[string] `key:"arazzo"` - Info marshaller.Node[Info] `key:"info"` - SourceDescriptions marshaller.Node[[]SourceDescription] `key:"sourceDescriptions" required:"true"` - Workflows marshaller.Node[[]Workflow] `key:"workflows" required:"true"` - Components marshaller.Node[*Components] `key:"components"` - Extensions core.Extensions `key:"extensions"` + Arazzo marshaller.Node[string] `key:"arazzo"` + Info marshaller.Node[Info] `key:"info"` + SourceDescriptions marshaller.Node[[]*SourceDescription] `key:"sourceDescriptions" required:"true"` + Workflows marshaller.Node[[]*Workflow] `key:"workflows" required:"true"` + Components marshaller.Node[*Components] `key:"components"` + Extensions core.Extensions `key:"extensions"` RootNode *yaml.Node Config *yml.Config diff --git a/arazzo/core/components.go b/arazzo/core/components.go index d7dfb7c..cc78ea1 100644 --- a/arazzo/core/components.go +++ b/arazzo/core/components.go @@ -12,9 +12,9 @@ import ( type Components struct { Inputs marshaller.Node[*sequencedmap.Map[string, core.JSONSchema]] `key:"inputs"` - Parameters marshaller.Node[*sequencedmap.Map[string, Parameter]] `key:"parameters"` - SuccessActions marshaller.Node[*sequencedmap.Map[string, SuccessAction]] `key:"successActions"` - FailureActions marshaller.Node[*sequencedmap.Map[string, FailureAction]] `key:"failureActions"` + Parameters marshaller.Node[*sequencedmap.Map[string, *Parameter]] `key:"parameters"` + SuccessActions marshaller.Node[*sequencedmap.Map[string, *SuccessAction]] `key:"successActions"` + FailureActions marshaller.Node[*sequencedmap.Map[string, *FailureAction]] `key:"failureActions"` Extensions coreExtensions.Extensions `key:"extensions"` RootNode *yaml.Node diff --git a/arazzo/core/failureaction.go b/arazzo/core/failureaction.go index 89d9c43..24deb99 100644 --- a/arazzo/core/failureaction.go +++ b/arazzo/core/failureaction.go @@ -9,14 +9,14 @@ import ( ) type FailureAction struct { - Name marshaller.Node[string] `key:"name"` - Type marshaller.Node[string] `key:"type"` - WorkflowID marshaller.Node[*Expression] `key:"workflowId"` - StepID marshaller.Node[*string] `key:"stepId"` - RetryAfter marshaller.Node[*float64] `key:"retryAfter"` - RetryLimit marshaller.Node[*int] `key:"retryLimit"` - Criteria marshaller.Node[[]Criterion] `key:"criteria"` - Extensions core.Extensions `key:"extensions"` + Name marshaller.Node[string] `key:"name"` + Type marshaller.Node[string] `key:"type"` + WorkflowID marshaller.Node[*Expression] `key:"workflowId"` + StepID marshaller.Node[*string] `key:"stepId"` + RetryAfter marshaller.Node[*float64] `key:"retryAfter"` + RetryLimit marshaller.Node[*int] `key:"retryLimit"` + Criteria marshaller.Node[[]*Criterion] `key:"criteria"` + Extensions core.Extensions `key:"extensions"` RootNode *yaml.Node } diff --git a/arazzo/core/requestbody.go b/arazzo/core/requestbody.go index 10759b4..61055ca 100644 --- a/arazzo/core/requestbody.go +++ b/arazzo/core/requestbody.go @@ -9,10 +9,10 @@ import ( ) type RequestBody struct { - ContentType marshaller.Node[*string] `key:"contentType"` - Payload marshaller.Node[ValueOrExpression] `key:"payload"` - Replacements marshaller.Node[[]PayloadReplacement] `key:"replacements"` - Extensions core.Extensions `key:"extensions"` + ContentType marshaller.Node[*string] `key:"contentType"` + Payload marshaller.Node[ValueOrExpression] `key:"payload"` + Replacements marshaller.Node[[]*PayloadReplacement] `key:"replacements"` + Extensions core.Extensions `key:"extensions"` RootNode *yaml.Node } diff --git a/arazzo/core/step.go b/arazzo/core/step.go index a4f7ed9..3c8954f 100644 --- a/arazzo/core/step.go +++ b/arazzo/core/step.go @@ -9,18 +9,18 @@ import ( ) type Step struct { - StepID marshaller.Node[string] `key:"stepId"` - Description marshaller.Node[*string] `key:"description"` - OperationID marshaller.Node[*Expression] `key:"operationId"` - OperationPath marshaller.Node[*string] `key:"operationPath"` - WorkflowID marshaller.Node[*Expression] `key:"workflowId"` - Parameters marshaller.Node[[]Reusable[Parameter]] `key:"parameters"` - RequestBody marshaller.Node[*RequestBody] `key:"requestBody"` - SuccessCriteria marshaller.Node[[]Criterion] `key:"successCriteria"` - OnSuccess marshaller.Node[[]Reusable[SuccessAction]] `key:"onSuccess"` - OnFailure marshaller.Node[[]Reusable[FailureAction]] `key:"onFailure"` - Outputs marshaller.Node[Outputs] `key:"outputs"` - Extensions core.Extensions `key:"extensions"` + StepID marshaller.Node[string] `key:"stepId"` + Description marshaller.Node[*string] `key:"description"` + OperationID marshaller.Node[*Expression] `key:"operationId"` + OperationPath marshaller.Node[*string] `key:"operationPath"` + WorkflowID marshaller.Node[*Expression] `key:"workflowId"` + Parameters marshaller.Node[[]*Reusable[Parameter]] `key:"parameters"` + RequestBody marshaller.Node[*RequestBody] `key:"requestBody"` + SuccessCriteria marshaller.Node[[]*Criterion] `key:"successCriteria"` + OnSuccess marshaller.Node[[]*Reusable[SuccessAction]] `key:"onSuccess"` + OnFailure marshaller.Node[[]*Reusable[FailureAction]] `key:"onFailure"` + Outputs marshaller.Node[Outputs] `key:"outputs"` + Extensions core.Extensions `key:"extensions"` RootNode *yaml.Node } diff --git a/arazzo/core/successaction.go b/arazzo/core/successaction.go index 3548f6e..49a96c7 100644 --- a/arazzo/core/successaction.go +++ b/arazzo/core/successaction.go @@ -9,12 +9,12 @@ import ( ) type SuccessAction struct { - Name marshaller.Node[string] `key:"name"` - Type marshaller.Node[string] `key:"type"` - WorkflowID marshaller.Node[*Expression] `key:"workflowId"` - StepID marshaller.Node[*string] `key:"stepId"` - Criteria marshaller.Node[[]Criterion] `key:"criteria"` - Extensions core.Extensions `key:"extensions"` + Name marshaller.Node[string] `key:"name"` + Type marshaller.Node[string] `key:"type"` + WorkflowID marshaller.Node[*Expression] `key:"workflowId"` + StepID marshaller.Node[*string] `key:"stepId"` + Criteria marshaller.Node[[]*Criterion] `key:"criteria"` + Extensions core.Extensions `key:"extensions"` RootNode *yaml.Node } diff --git a/arazzo/core/workflow.go b/arazzo/core/workflow.go index d93d0b7..4e29908 100644 --- a/arazzo/core/workflow.go +++ b/arazzo/core/workflow.go @@ -10,17 +10,17 @@ import ( ) type Workflow struct { - WorkflowID marshaller.Node[string] `key:"workflowId"` - Summary marshaller.Node[*string] `key:"summary"` - Description marshaller.Node[*string] `key:"description"` - Parameters marshaller.Node[[]Reusable[Parameter]] `key:"parameters"` - Inputs marshaller.Node[core.JSONSchema] `key:"inputs"` - DependsOn marshaller.Node[[]Expression] `key:"dependsOn"` - Steps marshaller.Node[[]Step] `key:"steps" required:"true"` - SuccessActions marshaller.Node[[]Reusable[SuccessAction]] `key:"successActions"` - FailureActions marshaller.Node[[]Reusable[FailureAction]] `key:"failureActions"` - Outputs marshaller.Node[Outputs] `key:"outputs"` - Extensions coreExtensions.Extensions `key:"extensions"` + WorkflowID marshaller.Node[string] `key:"workflowId"` + Summary marshaller.Node[*string] `key:"summary"` + Description marshaller.Node[*string] `key:"description"` + Parameters marshaller.Node[[]*Reusable[Parameter]] `key:"parameters"` + Inputs marshaller.Node[core.JSONSchema] `key:"inputs"` + DependsOn marshaller.Node[[]Expression] `key:"dependsOn"` + Steps marshaller.Node[[]*Step] `key:"steps" required:"true"` + SuccessActions marshaller.Node[[]*Reusable[SuccessAction]] `key:"successActions"` + FailureActions marshaller.Node[[]*Reusable[FailureAction]] `key:"failureActions"` + Outputs marshaller.Node[Outputs] `key:"outputs"` + Extensions coreExtensions.Extensions `key:"extensions"` RootNode *yaml.Node } diff --git a/arazzo/criterion/criterion.go b/arazzo/criterion/criterion.go index 6f3b9b2..7d88668 100644 --- a/arazzo/criterion/criterion.go +++ b/arazzo/criterion/criterion.go @@ -45,6 +45,9 @@ type CriterionExpressionType struct { // Version is the version of the criterion type. Version CriterionTypeVersion + // Valid indicates whether this model passed validation. + Valid bool + core core.CriterionExpressionType } @@ -89,6 +92,10 @@ func (c *CriterionExpressionType) Validate(opts ...validation.Option) []error { }) } + if len(errs) == 0 { + c.Valid = true + } + return errs } @@ -180,6 +187,9 @@ type Criterion struct { // Extensions provides a list of extensions to the Criterion object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Criterion } @@ -189,6 +199,11 @@ func (c *Criterion) GetCore() *core.Criterion { return &c.core } +// GetCondition will return the condition as a parsed condition object +func (c *Criterion) GetCondition() (*Condition, error) { + return newCondition(c.Condition) +} + // Validate will validate the criterion object against the Arazzo specification. func (c *Criterion) Validate(opts ...validation.Option) []error { errs := []error{} @@ -238,6 +253,10 @@ func (c *Criterion) Validate(opts ...validation.Option) []error { errs = append(errs, c.validateCondition(opts...)...) + if len(errs) == 0 { + c.Valid = true + } + return errs } @@ -250,7 +269,7 @@ func (c *Criterion) validateCondition(opts ...validation.Option) []error { switch c.Type.GetType() { case CriterionTypeSimple: cond, err := newCondition(c.Condition) - if err != nil { + if err != nil && c.Context == nil { errs = append(errs, &validation.Error{ Message: err.Error(), Line: conditionLine, diff --git a/arazzo/failureaction.go b/arazzo/failureaction.go index 4313b28..67b2693 100644 --- a/arazzo/failureaction.go +++ b/arazzo/failureaction.go @@ -44,6 +44,9 @@ type FailureAction struct { // Extensions provides a list of extensions to the FailureAction object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.FailureAction } @@ -59,7 +62,7 @@ func (f *FailureAction) GetCore() *core.FailureAction { // Requires an Arazzo object to be passed via validation options with validation.WithContextObject(). // If a Workflow object is provided via validation options with validation.WithContextObject() then // the FailureAction will be validated with the context of the workflow. -func (f FailureAction) Validate(ctx context.Context, opts ...validation.Option) []error { +func (f *FailureAction) Validate(ctx context.Context, opts ...validation.Option) []error { o := validation.NewOptions(opts...) a := validation.GetContextObject[Arazzo](o) @@ -180,5 +183,9 @@ func (f FailureAction) Validate(ctx context.Context, opts ...validation.Option) errs = append(errs, criterion.Validate(opts...)...) } + if len(errs) == 0 { + f.Valid = true + } + return errs } diff --git a/arazzo/info.go b/arazzo/info.go index a78ba4b..1a782fb 100644 --- a/arazzo/info.go +++ b/arazzo/info.go @@ -21,6 +21,9 @@ type Info struct { // Extensions provides a list of extensions to the Info object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Info } @@ -52,5 +55,9 @@ func (i *Info) Validate(ctx context.Context, opts ...validation.Option) []error }) } + if len(errs) == 0 { + i.Valid = true + } + return errs } diff --git a/arazzo/interfaces.go b/arazzo/interfaces.go index 9bb1894..c71982d 100644 --- a/arazzo/interfaces.go +++ b/arazzo/interfaces.go @@ -6,11 +6,12 @@ import ( "github.com/speakeasy-api/openapi/validation" ) -type validator interface { +type validator[T any] interface { + *T Validate(context.Context, ...validation.Option) []error } -type model[T any] interface { - validator - GetCore() *T +type model[C any] interface { + Validate(context.Context, ...validation.Option) []error + GetCore() *C } diff --git a/arazzo/parameter.go b/arazzo/parameter.go index 83b9a34..0eea6fb 100644 --- a/arazzo/parameter.go +++ b/arazzo/parameter.go @@ -22,8 +22,6 @@ const ( InHeader In = "header" // InCookie indicates that the parameter is in the cookie of the request. InCookie In = "cookie" - // InBody indicates that the parameter is in the body of the request. - InBody In = "body" ) // Parameter represents parameters that will be passed to a workflow or operation referenced by a step. @@ -37,6 +35,9 @@ type Parameter struct { // Extensions provides a list of extensions to the Parameter object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Parameter } @@ -51,7 +52,7 @@ func (p *Parameter) GetCore() *core.Parameter { // Validate will validate the parameter object against the Arazzo specification. // If an Workflow or Step object is provided via validation options with validation.WithContextObject() then // it will be validated in the context of that object. -func (p Parameter) Validate(ctx context.Context, opts ...validation.Option) []error { +func (p *Parameter) Validate(ctx context.Context, opts ...validation.Option) []error { errs := []error{} o := validation.NewOptions(opts...) @@ -77,7 +78,6 @@ func (p Parameter) Validate(ctx context.Context, opts ...validation.Option) []er case InQuery: case InHeader: case InCookie: - case InBody: default: if p.In == nil || in == "" { if w == nil && s != nil && s.WorkflowID == nil { @@ -91,7 +91,7 @@ func (p Parameter) Validate(ctx context.Context, opts ...validation.Option) []er if in != "" { errs = append(errs, &validation.Error{ - Message: fmt.Sprintf("in must be one of [%s]", strings.Join([]string{string(InPath), string(InQuery), string(InHeader), string(InCookie), string(InBody)}, ", ")), + Message: fmt.Sprintf("in must be one of [%s]", strings.Join([]string{string(InPath), string(InQuery), string(InHeader), string(InCookie)}, ", ")), Line: p.core.In.GetValueNodeOrRoot(p.core.RootNode).Line, Column: p.core.In.GetValueNodeOrRoot(p.core.RootNode).Column, }) @@ -124,5 +124,9 @@ func (p Parameter) Validate(ctx context.Context, opts ...validation.Option) []er } } + if len(errs) == 0 { + p.Valid = true + } + return errs } diff --git a/arazzo/payloadreplacement.go b/arazzo/payloadreplacement.go index f27c663..0966853 100644 --- a/arazzo/payloadreplacement.go +++ b/arazzo/payloadreplacement.go @@ -18,6 +18,9 @@ type PayloadReplacement struct { // Extensions provides a list of extensions to the PayloadReplacement object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.PayloadReplacement } @@ -75,5 +78,9 @@ func (p *PayloadReplacement) Validate(ctx context.Context, opts ...validation.Op } } + if len(errs) == 0 { + p.Valid = true + } + return errs } diff --git a/arazzo/requestbody.go b/arazzo/requestbody.go index 6cd1ad7..5d0f409 100644 --- a/arazzo/requestbody.go +++ b/arazzo/requestbody.go @@ -19,10 +19,13 @@ type RequestBody struct { // Payload is a static value or value containing expressions that will be used to populate the request body Payload Value // Replacements is a list of expressions that will be used to populate the request body in addition to any in the Payload field - Replacements []PayloadReplacement + Replacements []*PayloadReplacement // Extensions is a list of extensions to apply to the request body object Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.RequestBody } @@ -75,5 +78,9 @@ func (r *RequestBody) Validate(ctx context.Context, opts ...validation.Option) [ errs = append(errs, replacement.Validate(ctx, opts...)...) } + if len(errs) == 0 { + r.Valid = true + } + return errs } diff --git a/arazzo/reusable.go b/arazzo/reusable.go index 2430b33..959a2eb 100644 --- a/arazzo/reusable.go +++ b/arazzo/reusable.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "reflect" - "strings" "unicode" "unicode/utf8" @@ -18,29 +17,95 @@ import ( type ( // ReusableParameter represents a parameter that can either be referenced from components or declared inline in a workflow or step. - ReusableParameter = Reusable[Parameter, core.Parameter] + ReusableParameter = Reusable[Parameter, *Parameter, core.Parameter] // ReusableSuccessAction represents a success action that can either be referenced from components or declared inline in a workflow or step. - ReusableSuccessAction = Reusable[SuccessAction, core.SuccessAction] + ReusableSuccessAction = Reusable[SuccessAction, *SuccessAction, core.SuccessAction] // ReusableFailureAction represents a failure action that can either be referenced from components or declared inline in a workflow or step. - ReusableFailureAction = Reusable[FailureAction, core.FailureAction] + ReusableFailureAction = Reusable[FailureAction, *FailureAction, core.FailureAction] ) -type Reusable[T validator, C any] struct { +type Reusable[T any, V validator[T], C any] struct { + // Reference is the expression to the location of the reusable object. Reference *expression.Expression - Value Value - Object *T + // Value is any value provided alongside a parameter reusable object. + Value Value + // If this reusable object is not a reference, this will be the inline object for this node. + Object V + + // Valid indicates whether this reusable model passed validation. + Valid bool core core.Reusable[C] } // GetCore will return the low level representation of the reusable object. // Useful for accessing line and column numbers for various nodes in the backing yaml/json document. -func (r *Reusable[T, C]) GetCore() *core.Reusable[C] { +func (r *Reusable[T, V, C]) GetCore() *core.Reusable[C] { return &r.core } +// Get will return either the inline object or the object referenced by the reference. +func (r *Reusable[T, V, C]) Get(components *Components) *T { + if r.IsReference() { + return r.GetReferencedObject(components) + } else { + return r.Object + } +} + +func (r *Reusable[T, V, C]) IsReference() bool { + return r.Reference != nil +} + +func (r *Reusable[T, V, C]) GetReferencedObject(components *Components) *T { + if !r.IsReference() { + return nil + } + + typ, componentType, references, _ := r.Reference.GetParts() + + if typ != expression.ExpressionTypeComponents { + return nil + } + + if componentType == "" || len(references) != 1 { + return nil + } + + var component any + + switch componentType { + case "parameters": + param, ok := components.Parameters.Get(references[0]) + if !ok { + return nil + } + component = param + case "successActions": + successAction, ok := components.SuccessActions.Get(references[0]) + if !ok { + return nil + } + component = successAction + case "failureActions": + failureAction, ok := components.FailureActions.Get(references[0]) + if !ok { + return nil + } + component = failureAction + default: + return nil + } + + paramT, ok := component.(*T) + if !ok { + return nil + } + return paramT +} + // Validate will validate the reusable object against the Arazzo specification. -func (r *Reusable[T, C]) Validate(ctx context.Context, opts ...validation.Option) []error { +func (r *Reusable[T, V, C]) Validate(ctx context.Context, opts ...validation.Option) []error { o := validation.NewOptions(opts...) a := validation.GetContextObject[Arazzo](o) @@ -67,13 +132,17 @@ func (r *Reusable[T, C]) Validate(ctx context.Context, opts ...validation.Option if r.Reference != nil { errs = append(errs, r.validateReference(ctx, a, opts...)...) } else if r.Object != nil { - errs = append(errs, (*r.Object).Validate(ctx, opts...)...) + errs = append(errs, r.Object.Validate(ctx, opts...)...) + } + + if len(errs) == 0 { + r.Valid = true } return errs } -func (r *Reusable[T, C]) validateReference(ctx context.Context, a *Arazzo, opts ...validation.Option) []error { +func (r *Reusable[T, V, C]) validateReference(ctx context.Context, a *Arazzo, opts ...validation.Option) []error { if err := r.Reference.Validate(true); err != nil { return []error{ validation.Error{ @@ -83,7 +152,10 @@ func (r *Reusable[T, C]) validateReference(ctx context.Context, a *Arazzo, opts }, } } - if r.Reference.GetType() != expression.ExpressionTypeComponents { + + typ, componentType, references, _ := r.Reference.GetParts() + + if typ != expression.ExpressionTypeComponents { return []error{ validation.Error{ Message: fmt.Sprintf("reference must be a components expression, got %s", r.Reference.GetType()), @@ -93,19 +165,17 @@ func (r *Reusable[T, C]) validateReference(ctx context.Context, a *Arazzo, opts } } - parts := strings.Split(string(*r.Reference), ".") - if len(parts) != 3 { + if componentType == "" || len(references) != 1 { return []error{ validation.Error{ - Message: fmt.Sprintf("reference must be a components expression with 3 parts, got %d", len(parts)), + Message: fmt.Sprintf("reference must be a components expression with 3 parts, got %s", *r.Reference), Line: r.core.Reference.GetValueNodeOrRoot(r.core.RootNode).Line, Column: r.core.Reference.GetValueNodeOrRoot(r.core.RootNode).Column, }, } } - componentType := parts[1] - componentName := parts[2] + componentName := references[0] if a.Components == nil { return []error{ @@ -117,32 +187,32 @@ func (r *Reusable[T, C]) validateReference(ctx context.Context, a *Arazzo, opts } } - typ := reflect.TypeOf(r.Object).Elem() + objType := reflect.TypeOf(r.Object).Elem() switch componentType { case "parameters": - return validateComponentReference(ctx, validateComponentReferenceArgs[Parameter]{ + return validateComponentReference(ctx, validateComponentReferenceArgs[*Parameter]{ componentType: componentType, componentName: componentName, - typ: typ, + typ: objType, components: a.Components.Parameters, reference: r.Reference, referenceValueNode: r.core.Reference.GetValueNodeOrRoot(r.core.RootNode), }, opts...) case "successActions": - return validateComponentReference(ctx, validateComponentReferenceArgs[SuccessAction]{ + return validateComponentReference(ctx, validateComponentReferenceArgs[*SuccessAction]{ componentType: componentType, componentName: componentName, - typ: typ, + typ: objType, components: a.Components.SuccessActions, reference: r.Reference, referenceValueNode: r.core.Reference.GetValueNodeOrRoot(r.core.RootNode), }, opts...) case "failureActions": - return validateComponentReference(ctx, validateComponentReferenceArgs[FailureAction]{ + return validateComponentReference(ctx, validateComponentReferenceArgs[*FailureAction]{ componentType: componentType, componentName: componentName, - typ: typ, + typ: objType, components: a.Components.FailureActions, reference: r.Reference, referenceValueNode: r.core.Reference.GetValueNodeOrRoot(r.core.RootNode), @@ -167,7 +237,7 @@ type validateComponentReferenceArgs[T any] struct { referenceValueNode *yaml.Node } -func validateComponentReference[T validator](ctx context.Context, args validateComponentReferenceArgs[T], opts ...validation.Option) []error { +func validateComponentReference[T any, V validator[T]](ctx context.Context, args validateComponentReferenceArgs[V], opts ...validation.Option) []error { typ := reflect.TypeOf((*T)(nil)).Elem() if args.typ != typ { @@ -201,10 +271,6 @@ func validateComponentReference[T validator](ctx context.Context, args validateC } } - if args.componentType != "parameters" { - return nil - } - return component.Validate(ctx, opts...) } diff --git a/arazzo/sourcedescription.go b/arazzo/sourcedescription.go index 44b58a5..e2f9333 100644 --- a/arazzo/sourcedescription.go +++ b/arazzo/sourcedescription.go @@ -12,13 +12,13 @@ import ( ) // SourceDescriptions represents a list of SourceDescription objects that describe the source of the data that the workflow is orchestrating. -type SourceDescriptions []SourceDescription +type SourceDescriptions []*SourceDescription // Find will return the first SourceDescription object with the provided name. func (s SourceDescriptions) Find(name string) *SourceDescription { for _, sourceDescription := range s { if sourceDescription.Name == name { - return &sourceDescription + return sourceDescription } } return nil @@ -45,6 +45,9 @@ type SourceDescription struct { // Extensions provides a list of extensions to the SourceDescription object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.SourceDescription } @@ -95,5 +98,9 @@ func (s *SourceDescription) Validate(ctx context.Context, opts ...validation.Opt }) } + if len(errs) == 0 { + s.Valid = true + } + return errs } diff --git a/arazzo/step.go b/arazzo/step.go index 2081924..ae3e5bb 100644 --- a/arazzo/step.go +++ b/arazzo/step.go @@ -14,13 +14,13 @@ import ( ) // Steps represents a list of Step objects that describe the operations to be performed in the workflow. -type Steps []Step +type Steps []*Step // Find will return the first Step object with the provided id. func (s Steps) Find(id string) *Step { for _, step := range s { if step.StepID == id { - return &step + return step } } return nil @@ -39,20 +39,23 @@ type Step struct { // WorkflowID is a workflowId or expression to a workflow in a SourceDescription that the step relates to. Mutually exclusive with OperationID & OperationPath. WorkflowID *expression.Expression // Parameters is a list of Parameters that will be passed to the referenced operation or workflow. These will override any matching parameters defined at the workflow level. - Parameters []ReusableParameter + Parameters []*ReusableParameter // RequestBody is the request body to be passed to the referenced operation. RequestBody *RequestBody // SuccessCriteria is a list of criteria that must be met for the step to be considered successful. - SuccessCriteria []criterion.Criterion + SuccessCriteria []*criterion.Criterion // OnSuccess is a list of SuccessActions that will be executed if the step is successful. - OnSuccess []ReusableSuccessAction + OnSuccess []*ReusableSuccessAction // OnFailure is a list of FailureActions that will be executed if the step is unsuccessful. - OnFailure []ReusableFailureAction + OnFailure []*ReusableFailureAction // Outputs is a list of outputs that will be returned by the step. Outputs Outputs // Extensions provides a list of extensions to the Step object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Step } @@ -397,5 +400,9 @@ func (s *Step) Validate(ctx context.Context, opts ...validation.Option) []error } } + if len(errs) == 0 { + s.Valid = true + } + return errs } diff --git a/arazzo/successaction.go b/arazzo/successaction.go index 21a57a3..bc17e9b 100644 --- a/arazzo/successaction.go +++ b/arazzo/successaction.go @@ -38,6 +38,9 @@ type SuccessAction struct { // Extensions provides a list of extensions to the SuccessAction object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.SuccessAction } @@ -51,7 +54,7 @@ func (s *SuccessAction) GetCore() *core.SuccessAction { // Validate will validate the success action object against the Arazzo specification. // Requires an Arazzo object to be passed via validation options with validation.WithContextObject(). -func (s SuccessAction) Validate(ctx context.Context, opts ...validation.Option) []error { +func (s *SuccessAction) Validate(ctx context.Context, opts ...validation.Option) []error { o := validation.NewOptions(opts...) a := validation.GetContextObject[Arazzo](o) @@ -113,6 +116,10 @@ func (s SuccessAction) Validate(ctx context.Context, opts ...validation.Option) errs = append(errs, criterion.Validate(opts...)...) } + if len(errs) == 0 { + s.Valid = true + } + return errs } diff --git a/arazzo/walk.go b/arazzo/walk.go index ccbcc67..eda53ff 100644 --- a/arazzo/walk.go +++ b/arazzo/walk.go @@ -18,11 +18,11 @@ type Matcher struct { Info func(*Info) error SourceDescription func(*SourceDescription) error Workflow func(*Workflow) error - ReusableParameter func(ReusableParameter) error + ReusableParameter func(*ReusableParameter) error JSONSchema func(oas31.JSONSchema) error Step func(*Step) error - ReusableSuccessAction func(ReusableSuccessAction) error - ReusableFailureAction func(ReusableFailureAction) error + ReusableSuccessAction func(*ReusableSuccessAction) error + ReusableFailureAction func(*ReusableFailureAction) error Components func(*Components) error Parameter func(*Parameter) error SuccessAction func(*SuccessAction) error @@ -59,7 +59,7 @@ func Walk(ctx context.Context, arazzo *Arazzo, visit VisitFunc) error { } for _, sd := range arazzo.SourceDescriptions { - if err := visit(ctx, getSourceDescriptionMatchFunc(&sd), getArazzoMatchFunc(arazzo), arazzo); err != nil { + if err := visit(ctx, getSourceDescriptionMatchFunc(sd), getArazzoMatchFunc(arazzo), arazzo); err != nil { if errors.Is(err, ErrTerminate) { return nil } @@ -68,7 +68,7 @@ func Walk(ctx context.Context, arazzo *Arazzo, visit VisitFunc) error { } for _, wf := range arazzo.Workflows { - if err := walkWorkflow(ctx, &wf, getArazzoMatchFunc(arazzo), arazzo, visit); err != nil { + if err := walkWorkflow(ctx, wf, getArazzoMatchFunc(arazzo), arazzo, visit); err != nil { if errors.Is(err, ErrTerminate) { return nil } @@ -87,6 +87,10 @@ func Walk(ctx context.Context, arazzo *Arazzo, visit VisitFunc) error { } func walkWorkflow(ctx context.Context, workflow *Workflow, parent MatchFunc, arazzo *Arazzo, visit VisitFunc) error { + if workflow == nil { + return nil + } + if err := visit(ctx, getWorkflowMatchFunc(workflow), parent, arazzo); err != nil { return err } @@ -102,7 +106,7 @@ func walkWorkflow(ctx context.Context, workflow *Workflow, parent MatchFunc, ara } for _, step := range workflow.Steps { - if err := walkStep(ctx, &step, getWorkflowMatchFunc(workflow), arazzo, visit); err != nil { + if err := walkStep(ctx, step, getWorkflowMatchFunc(workflow), arazzo, visit); err != nil { return err } } @@ -123,6 +127,10 @@ func walkWorkflow(ctx context.Context, workflow *Workflow, parent MatchFunc, ara } func walkStep(ctx context.Context, step *Step, parent MatchFunc, arazzo *Arazzo, visit VisitFunc) error { + if step == nil { + return nil + } + if err := visit(ctx, getStepMatchFunc(step), parent, arazzo); err != nil { return err } @@ -149,6 +157,10 @@ func walkStep(ctx context.Context, step *Step, parent MatchFunc, arazzo *Arazzo, } func walkComponents(ctx context.Context, components *Components, parent MatchFunc, arazzo *Arazzo, visit VisitFunc) error { + if components == nil { + return nil + } + if err := visit(ctx, getComponentsMatchFunc(components), parent, arazzo); err != nil { return err } @@ -160,19 +172,19 @@ func walkComponents(ctx context.Context, components *Components, parent MatchFun } for _, parameter := range components.Parameters.All() { - if err := visit(ctx, getParameterMatchFunc(¶meter), getComponentsMatchFunc(components), arazzo); err != nil { + if err := visit(ctx, getParameterMatchFunc(parameter), getComponentsMatchFunc(components), arazzo); err != nil { return err } } for _, successAction := range components.SuccessActions.All() { - if err := visit(ctx, getSuccessActionMatchFunc(&successAction), getComponentsMatchFunc(components), arazzo); err != nil { + if err := visit(ctx, getSuccessActionMatchFunc(successAction), getComponentsMatchFunc(components), arazzo); err != nil { return err } } for _, failureAction := range components.FailureActions.All() { - if err := visit(ctx, getFailureActionMatchFunc(&failureAction), getComponentsMatchFunc(components), arazzo); err != nil { + if err := visit(ctx, getFailureActionMatchFunc(failureAction), getComponentsMatchFunc(components), arazzo); err != nil { return err } } @@ -216,7 +228,7 @@ func getWorkflowMatchFunc(workflow *Workflow) MatchFunc { } } -func getReusableParameterMatchFunc(reusable ReusableParameter) MatchFunc { +func getReusableParameterMatchFunc(reusable *ReusableParameter) MatchFunc { return func(m Matcher) error { if m.ReusableParameter != nil { return m.ReusableParameter(reusable) @@ -243,7 +255,7 @@ func getStepMatchFunc(step *Step) MatchFunc { } } -func getReusableSuccessActionMatchFunc(successAction ReusableSuccessAction) MatchFunc { +func getReusableSuccessActionMatchFunc(successAction *ReusableSuccessAction) MatchFunc { return func(m Matcher) error { if m.ReusableSuccessAction != nil { return m.ReusableSuccessAction(successAction) @@ -252,7 +264,7 @@ func getReusableSuccessActionMatchFunc(successAction ReusableSuccessAction) Matc } } -func getReusableFailureActionMatchFunc(failureAction ReusableFailureAction) MatchFunc { +func getReusableFailureActionMatchFunc(failureAction *ReusableFailureAction) MatchFunc { return func(m Matcher) error { if m.ReusableFailureAction != nil { return m.ReusableFailureAction(failureAction) diff --git a/arazzo/workflow.go b/arazzo/workflow.go index 4146d3f..89e4355 100644 --- a/arazzo/workflow.go +++ b/arazzo/workflow.go @@ -14,13 +14,13 @@ import ( ) // Workflows provides a list of Workflow objects that describe the orchestration of API calls. -type Workflows []Workflow +type Workflows []*Workflow // Find will return the first workflow with the matching workflowId. func (w Workflows) Find(id string) *Workflow { for _, workflow := range w { if workflow.WorkflowID == id { - return &workflow + return workflow } } return nil @@ -35,7 +35,7 @@ type Workflow struct { // Description is a longer description of the purpose of the workflow. May contain CommonMark syntax. Description *string // Parameters is a list of Parameters that will be passed to the referenced operation or workflow. - Parameters []ReusableParameter + Parameters []*ReusableParameter // Inputs is a JSON Schema containing a set of inputs that will be passed to the referenced workflow. Inputs oas31.JSONSchema // DependsOn is a list of workflowIds (or expressions to workflows) that must succeed before this workflow can be executed. @@ -43,14 +43,17 @@ type Workflow struct { // Steps is a list of steps that will be executed in the order they are listed. Steps Steps // SuccessActions is a list of actions that will be executed by each step in the workflow if the step succeeds. Can be overridden by the step. - SuccessActions []ReusableSuccessAction + SuccessActions []*ReusableSuccessAction // FailureActions is a list of actions that will be executed by each step in the workflow if the step fails. Can be overridden by the step. - FailureActions []ReusableFailureAction + FailureActions []*ReusableFailureAction // Outputs is a set of outputs that will be returned by the workflow. Outputs Outputs // Extensions provides a list of extensions to the Workflow object. Extensions *extensions.Extensions + // Valid indicates whether this model passed validation. + Valid bool + core core.Workflow } @@ -166,5 +169,9 @@ func (w *Workflow) Validate(ctx context.Context, opts ...validation.Option) []er errs = append(errs, parameter.Validate(ctx, opts...)...) } + if len(errs) == 0 { + w.Valid = true + } + return errs } diff --git a/extensions/extensions.go b/extensions/extensions.go index 371495e..b370530 100644 --- a/extensions/extensions.go +++ b/extensions/extensions.go @@ -3,12 +3,17 @@ package extensions import ( "context" + "github.com/speakeasy-api/openapi/errors" "github.com/speakeasy-api/openapi/extensions/core" "github.com/speakeasy-api/openapi/marshaller" "github.com/speakeasy-api/openapi/sequencedmap" "gopkg.in/yaml.v3" ) +const ( + ErrNotFound = errors.Error("not found") +) + // Extension represents a single extension to an object, in its raw form. type Extension = *yaml.Node @@ -57,7 +62,16 @@ func (e *Extensions) SetCore(core any) { e.core = c } +// UnmarshalExtensionModel will unmarshal the extension into a model and its associated core model. func UnmarshalExtensionModel[H any, L any](ctx context.Context, e *Extensions, ext string, m *H) error { + if e == nil { + return ErrNotFound.Wrap(errors.New("extensions is nil")) + } + + if !e.Has(ext) { + return ErrNotFound + } + c, err := core.UnmarshalExtensionModel[L](ctx, e.core, ext) if err != nil { return err @@ -72,3 +86,24 @@ func UnmarshalExtensionModel[H any, L any](ctx context.Context, e *Extensions, e return nil } + +// GetExtensionValue will return the value of the extension. Useful for scalar values or where a model without a core is required. +func GetExtensionValue[T any](e *Extensions, ext string) (*T, error) { + var zero *T + + if e == nil { + return zero, ErrNotFound.Wrap(errors.New("extensions is nil")) + } + + node := e.GetOrZero(ext) + if node == nil { + return zero, ErrNotFound + } + + var t T + if err := node.Decode(&t); err != nil { + return zero, err + } + + return &t, nil +} diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go index 4135a39..29115ff 100644 --- a/extensions/extensions_test.go +++ b/extensions/extensions_test.go @@ -32,7 +32,7 @@ type CoreModelWithExtensions struct { type TestModel struct { Name string - Value *yaml.Node + Value yaml.Node core TestCoreModel //nolint:unused } @@ -47,16 +47,70 @@ type TestCoreModel struct { func TestUnmarshalExtensionModel_Success(t *testing.T) { ctx := context.Background() - data, err := io.ReadAll(bytes.NewReader([]byte(` + m := getTestModelWithExtensions(ctx, t, ` test: hello world x-speakeasy-test: name: test - value: 1 -`))) + value: 1`) + + var testModel TestModel + err := extensions.UnmarshalExtensionModel[TestModel, TestCoreModel](ctx, m.Extensions, "x-speakeasy-test", &testModel) + require.NoError(t, err) + + assert.Equal(t, "test", testModel.Name) + assert.Equal(t, *testutils.CreateIntYamlNode(1, 5, 10), testModel.Value) +} + +func TestGetExtensionValue_Success(t *testing.T) { + ctx := context.Background() + + m := getTestModelWithExtensions(ctx, t, ` +test: hello world +x-int: 1 +x-string: hi +x-bool: true +x-simple-map: + key1: value1 + key2: value2 +x-simple-model: + name: test + value: 1`) + + intVal, err := extensions.GetExtensionValue[int](m.Extensions, "x-int") + require.NoError(t, err) + require.NotNil(t, intVal) + assert.Equal(t, 1, *intVal) + + stringVal, err := extensions.GetExtensionValue[string](m.Extensions, "x-string") + require.NoError(t, err) + require.NotNil(t, stringVal) + assert.Equal(t, "hi", *stringVal) + + boolVal, err := extensions.GetExtensionValue[bool](m.Extensions, "x-bool") + require.NoError(t, err) + require.NotNil(t, boolVal) + assert.Equal(t, true, *boolVal) + + simpleMapVal, err := extensions.GetExtensionValue[map[string]string](m.Extensions, "x-simple-map") + require.NoError(t, err) + require.NotNil(t, simpleMapVal) + assert.Equal(t, map[string]string{"key1": "value1", "key2": "value2"}, *simpleMapVal) + + simpleModelVal, err := extensions.GetExtensionValue[TestModel](m.Extensions, "x-simple-model") + require.NoError(t, err) + require.NotNil(t, simpleModelVal) + assert.Equal(t, "test", simpleModelVal.Name) + assert.Equal(t, *testutils.CreateIntYamlNode(1, 11, 10), simpleModelVal.Value) +} + +func getTestModelWithExtensions(ctx context.Context, t *testing.T, data string) *ModelWithExtensions { + t.Helper() + + d, err := io.ReadAll(bytes.NewReader([]byte(data))) require.NoError(t, err) var root yaml.Node - err = yaml.Unmarshal(data, &root) + err = yaml.Unmarshal(d, &root) require.NoError(t, err) var c CoreModelWithExtensions @@ -67,10 +121,5 @@ x-speakeasy-test: err = marshaller.PopulateModel(c, m) require.NoError(t, err) - var testModel TestModel - err = extensions.UnmarshalExtensionModel[TestModel, TestCoreModel](ctx, m.Extensions, "x-speakeasy-test", &testModel) - require.NoError(t, err) - - assert.Equal(t, "test", testModel.Name) - assert.Equal(t, testutils.CreateIntYamlNode(1, 5, 10), testModel.Value) + return m } diff --git a/jsonschema/oas31/jsonschema.go b/jsonschema/oas31/jsonschema.go index e33f405..ea6013e 100644 --- a/jsonschema/oas31/jsonschema.go +++ b/jsonschema/oas31/jsonschema.go @@ -80,6 +80,8 @@ type Schema struct { Schema *string Extensions *extensions.Extensions + Valid bool + core core.Schema } diff --git a/jsonschema/oas31/validation.go b/jsonschema/oas31/validation.go index 4d977ac..bfe60c2 100644 --- a/jsonschema/oas31/validation.go +++ b/jsonschema/oas31/validation.go @@ -65,6 +65,8 @@ func (js *Schema) Validate(ctx context.Context, opts ...validation.Option) []err } } + js.Valid = true + return nil } diff --git a/pointer/pointer.go b/pointer/pointer.go index b39f227..258597b 100644 --- a/pointer/pointer.go +++ b/pointer/pointer.go @@ -5,3 +5,13 @@ package pointer func From[T any](t T) *T { return &t } + +// ValueOrZero will return the value of the pointer or the zero value if the pointer is nil. +func ValueOrZero[T any](v *T) T { + if v == nil { + var zero T + return zero + } + + return *v +} diff --git a/validation/errors.go b/validation/errors.go index 1c3b8b0..8d313ec 100644 --- a/validation/errors.go +++ b/validation/errors.go @@ -1,6 +1,8 @@ package validation -import "fmt" +import ( + "fmt" +) // Error represents a validation error and the line and column where it occurred // TODO allow getting the JSON path for line/column for validation errors