mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-10 04:20:24 +00:00
(feat): Upgraded circular error handling experience #27
Thinking more about how to know what to resolve and what not to means depending on circular reference knowledge. So using an idea from @TristanSpeakEasy, the `*resolver.ResolvingError` now contains a pointer to the original circular error, and it also implements the `Error` interface correctly which allows it simple passage up the chain, without losing fidelity. Added new documentation example as well Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
@@ -146,9 +145,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
|
||||
|
||||
if len(resolvingErrors) > 0 {
|
||||
for r := range resolvingErrors {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("%s (%s) [%d:%d]", resolvingErrors[r].Error.Error(),
|
||||
resolvingErrors[r].Path, resolvingErrors[r].Node.Line, resolvingErrors[r].Node.Column))
|
||||
errors = append(errors, resolvingErrors[r])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package v3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
@@ -34,9 +33,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
||||
|
||||
if len(resolvingErrors) > 0 {
|
||||
for r := range resolvingErrors {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("%s: %s [%d:%d]", resolvingErrors[r].Error.Error(),
|
||||
resolvingErrors[r].Path, resolvingErrors[r].Node.Line, resolvingErrors[r].Node.Column))
|
||||
errors = append(errors, resolvingErrors[r])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ package libopenapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/resolver"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -477,3 +479,58 @@ func TestDocument_Paths_As_Array(t *testing.T) {
|
||||
v3Model, _ := doc.BuildV3Model()
|
||||
assert.NotNil(t, v3Model)
|
||||
}
|
||||
|
||||
// If you want to know more about circular references that have been found
|
||||
// during the parsing/indexing/building of a document, you can capture the
|
||||
// []errors thrown which are pointers to *resolver.ResolvingError
|
||||
func ExampleNewDocument_circular_references() {
|
||||
|
||||
// create a specification with an obvious and deliberate circular reference
|
||||
spec := `openapi: "3.1"
|
||||
components:
|
||||
schemas:
|
||||
One:
|
||||
description: "test one"
|
||||
properties:
|
||||
things:
|
||||
"$ref": "#/components/schemas/Two"
|
||||
Two:
|
||||
description: "test two"
|
||||
properties:
|
||||
testThing:
|
||||
"$ref": "#/components/schemas/One"
|
||||
`
|
||||
// create a new document from specification bytes
|
||||
doc, err := NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
_, errs := doc.BuildV3Model()
|
||||
|
||||
// extract resolving error
|
||||
resolvingError := errs[0]
|
||||
|
||||
// resolving error is a pointer to *resolver.ResolvingError
|
||||
// which provides access to rich details about the error.
|
||||
circularReference := resolvingError.(*resolver.ResolvingError).CircularReference
|
||||
|
||||
// capture the journey with all details
|
||||
var buf strings.Builder
|
||||
for n := range circularReference.Journey {
|
||||
|
||||
// add the full definition name to the journey.
|
||||
buf.WriteString(circularReference.Journey[n].Definition)
|
||||
if n < len(circularReference.Journey)-1 {
|
||||
buf.WriteString(" -> ")
|
||||
}
|
||||
}
|
||||
|
||||
// print out the journey and the loop point.
|
||||
fmt.Printf("Journey: %s\n", buf.String())
|
||||
fmt.Printf("Loop Point: %s", circularReference.LoopPoint.Definition)
|
||||
// Output: Journey: #/components/schemas/Two -> #/components/schemas/One -> #/components/schemas/Two
|
||||
// Loop Point: #/components/schemas/Two
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,22 @@ import (
|
||||
|
||||
// ResolvingError represents an issue the resolver had trying to stitch the tree together.
|
||||
type ResolvingError struct {
|
||||
Error error
|
||||
// ErrorRef is the error thrown by the resolver
|
||||
ErrorRef error
|
||||
|
||||
// Node is the *yaml.Node reference that contains the resolving error
|
||||
Node *yaml.Node
|
||||
|
||||
// Path is the shortened journey taken by the resolver
|
||||
Path string
|
||||
|
||||
// CircularReference is set if the error is a reference to the circular reference.
|
||||
CircularReference *index.CircularReferenceResult
|
||||
}
|
||||
|
||||
func (r *ResolvingError) Error() string {
|
||||
return fmt.Sprintf("%s: %s [%d:%d]", r.ErrorRef.Error(),
|
||||
r.Path, r.Node.Line, r.Node.Column)
|
||||
}
|
||||
|
||||
// Resolver will use a *index.SpecIndex to stitch together a resolved root tree using all the discovered
|
||||
@@ -106,7 +119,7 @@ func (resolver *Resolver) Resolve() []*ResolvingError {
|
||||
|
||||
for _, circRef := range resolver.circularReferences {
|
||||
resolver.resolvingErrors = append(resolver.resolvingErrors, &ResolvingError{
|
||||
Error: fmt.Errorf("Circular reference detected: %s", circRef.Start.Name),
|
||||
ErrorRef: fmt.Errorf("Circular reference detected: %s", circRef.Start.Name),
|
||||
Node: circRef.LoopPoint.Node,
|
||||
Path: circRef.GenerateJourneyPath(),
|
||||
})
|
||||
@@ -135,9 +148,10 @@ func (resolver *Resolver) CheckForCircularReferences() []*ResolvingError {
|
||||
}
|
||||
for _, circRef := range resolver.circularReferences {
|
||||
resolver.resolvingErrors = append(resolver.resolvingErrors, &ResolvingError{
|
||||
Error: fmt.Errorf("Circular reference detected: %s", circRef.Start.Name),
|
||||
ErrorRef: fmt.Errorf("Circular reference detected: %s", circRef.Start.Name),
|
||||
Node: circRef.LoopPoint.Node,
|
||||
Path: circRef.GenerateJourneyPath(),
|
||||
CircularReference: circRef,
|
||||
})
|
||||
}
|
||||
// update our index with any circular refs we found.
|
||||
@@ -231,7 +245,7 @@ func (resolver *Resolver) extractRelatives(node *yaml.Node,
|
||||
// TODO handle error, missing ref, can't resolve.
|
||||
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
||||
err := &ResolvingError{
|
||||
Error: fmt.Errorf("cannot resolve reference `%s`, it's missing", value),
|
||||
ErrorRef: fmt.Errorf("cannot resolve reference `%s`, it's missing", value),
|
||||
Node: n,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user