diff --git a/datamodel/low/v3/path_item.go b/datamodel/low/v3/path_item.go index 5d7146aa..76ca5c5a 100644 --- a/datamodel/low/v3/path_item.go +++ b/datamodel/low/v3/path_item.go @@ -5,6 +5,7 @@ package v3 import ( "context" + "errors" "fmt" "hash/maphash" "sort" @@ -408,6 +409,10 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind // all operations have been superficially built, // now we need to build out the operation, we will do this asynchronously for speed. + // build errors are collected rather than returned early, so every operation is fully + // constructed even when a sibling operation fails to build. + var opBuildErrors []error + var opBuildErrorsLock sync.Mutex translateFunc := func(_ int, op low.NodeReference[*Operation]) (any, error) { ref := "" var refNode *yaml.Node @@ -421,14 +426,13 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind op.Value.Reference.SetReference(ref, refNode) } if err != nil { - return nil, err + opBuildErrorsLock.Lock() + opBuildErrors = append(opBuildErrors, err) + opBuildErrorsLock.Unlock() } return nil, nil } - err := datamodel.TranslateSliceParallel[low.NodeReference[*Operation], any](ops, translateFunc, nil) - if err != nil { - return err - } + _ = datamodel.TranslateSliceParallel[low.NodeReference[*Operation], any](ops, translateFunc, nil) // assign additionalOperations if any were found if additionalOps != nil && additionalOps.Len() > 0 { @@ -438,12 +442,15 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind extrOps = append(extrOps, appVal) } - err = datamodel.TranslateSliceParallel[low.NodeReference[*Operation], any](extrOps, translateFunc, nil) + _ = datamodel.TranslateSliceParallel[low.NodeReference[*Operation], any](extrOps, translateFunc, nil) p.AdditionalOperations = low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.NodeReference[*Operation]]]{ Value: additionalOps, } } + if len(opBuildErrors) > 0 { + return errors.Join(opBuildErrors...) + } return nil } diff --git a/datamodel/low/v3/path_item_test.go b/datamodel/low/v3/path_item_test.go index fdf6ff2b..a1c37917 100644 --- a/datamodel/low/v3/path_item_test.go +++ b/datamodel/low/v3/path_item_test.go @@ -119,6 +119,35 @@ get: assert.NotNil(t, n.RootNode) } +func TestPathItem_Build_NullParameters_BuildsAllOperations(t *testing.T) { + yml := `post: + operationId: createThing + parameters: +put: + operationId: replaceThing + parameters: +delete: + operationId: deleteThing + parameters:` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + idx := index.NewSpecIndex(&idxNode) + + var n PathItem + _ = low.BuildModel(idxNode.Content[0], &n) + err := n.Build(context.Background(), nil, idxNode.Content[0], idx) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "input is not an array") + + // every operation must be fully built out, even though a sibling failed to build first. + assert.NotNil(t, n.Post.Value.Reference) + assert.NotNil(t, n.Put.Value.Reference) + assert.NotNil(t, n.Delete.Value.Reference) + assert.False(t, n.Put.Value.IsReference()) +} + func TestPathItem_AdditionalOperations(t *testing.T) { yml := `get: description: standard get operation