Skip to content

Commit 44282c7

Browse files
committed
added auto-attach of source documents.
I didn’t like the previous design, so now it feels more fluid
1 parent a8746b9 commit 44282c7

File tree

6 files changed

+162
-114
lines changed

6 files changed

+162
-114
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
libopenapi has full support for OpenAPI 3, 3.1 and 3.2. It can handle the largest and most
1515
complex specifications you can think of.
1616

17+
Overlays and Arazzo are also fully supported.
18+
1719
---
1820

1921
## Sponsors & users
@@ -78,6 +80,7 @@ See all the documentation at https://pb33f.io/libopenapi/
7880
- [Bundling Specs](https://pb33f.io/libopenapi/bundling/)
7981
- [What Changed / Diff Engine](https://pb33f.io/libopenapi/what-changed/)
8082
- [Overlays](https://pb33f.io/libopenapi/overlays/)
83+
- [Arazzo](https://pb33f.io/libopenapi/arazzo/)
8184
- [FAQ](https://pb33f.io/libopenapi/faq/)
8285
- [About libopenapi](https://pb33f.io/libopenapi/about/)
8386
---

arazzo/coverage_test.go

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/pb33f/libopenapi/arazzo/expression"
1818
high "github.com/pb33f/libopenapi/datamodel/high/arazzo"
19+
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
1920
"github.com/pb33f/libopenapi/orderedmap"
2021
"github.com/stretchr/testify/assert"
2122
"github.com/stretchr/testify/require"
@@ -1486,7 +1487,7 @@ func TestResolveSources_FactoryError(t *testing.T) {
14861487
HTTPHandler: func(_ string) ([]byte, error) {
14871488
return []byte("content"), nil
14881489
},
1489-
OpenAPIFactory: func(_ string, _ []byte) (any, error) {
1490+
OpenAPIFactory: func(_ string, _ []byte) (*v3high.Document, error) {
14901491
return nil, fmt.Errorf("parse failed")
14911492
},
14921493
}
@@ -1506,8 +1507,8 @@ func TestResolveSources_DefaultTypeIsOpenAPI(t *testing.T) {
15061507
HTTPHandler: func(_ string) ([]byte, error) {
15071508
return []byte("content"), nil
15081509
},
1509-
OpenAPIFactory: func(u string, b []byte) (any, error) {
1510-
return "doc", nil
1510+
OpenAPIFactory: func(u string, b []byte) (*v3high.Document, error) {
1511+
return &v3high.Document{}, nil
15111512
},
15121513
}
15131514
resolved, err := ResolveSources(doc, config)
@@ -1911,54 +1912,53 @@ func TestResolveFilePath_EncodedPath(t *testing.T) {
19111912
}
19121913

19131914
// ---------------------------------------------------------------------------
1914-
// factoryForType
1915+
// ResolveSources - missing factory and unknown type
19151916
// ---------------------------------------------------------------------------
19161917

1917-
func TestFactoryForType_OpenAPIWithFactory(t *testing.T) {
1918-
config := &ResolveConfig{
1919-
OpenAPIFactory: func(u string, b []byte) (any, error) {
1920-
return "openapi-doc", nil
1918+
func TestResolveSources_MissingOpenAPIFactory(t *testing.T) {
1919+
doc := &high.Arazzo{
1920+
SourceDescriptions: []*high.SourceDescription{
1921+
{Name: "api", URL: "https://example.com/api.yaml", Type: "openapi"},
19211922
},
19221923
}
1923-
factory, err := factoryForType("openapi", config)
1924-
require.NoError(t, err)
1925-
require.NotNil(t, factory)
1926-
doc, err := factory("url", nil)
1927-
require.NoError(t, err)
1928-
assert.Equal(t, "openapi-doc", doc)
1929-
}
1930-
1931-
func TestFactoryForType_ArazzoWithFactory(t *testing.T) {
19321924
config := &ResolveConfig{
1933-
ArazzoFactory: func(u string, b []byte) (any, error) {
1934-
return "arazzo-doc", nil
1925+
HTTPHandler: func(_ string) ([]byte, error) {
1926+
return []byte("content"), nil
19351927
},
19361928
}
1937-
factory, err := factoryForType("arazzo", config)
1938-
require.NoError(t, err)
1939-
require.NotNil(t, factory)
1940-
doc, err := factory("url", nil)
1941-
require.NoError(t, err)
1942-
assert.Equal(t, "arazzo-doc", doc)
1943-
}
1944-
1945-
func TestFactoryForType_OpenAPINilFactory(t *testing.T) {
1946-
config := &ResolveConfig{}
1947-
_, err := factoryForType("openapi", config)
1929+
_, err := ResolveSources(doc, config)
19481930
require.Error(t, err)
19491931
assert.Contains(t, err.Error(), "no OpenAPIFactory configured")
19501932
}
19511933

1952-
func TestFactoryForType_ArazzoNilFactory(t *testing.T) {
1953-
config := &ResolveConfig{}
1954-
_, err := factoryForType("arazzo", config)
1934+
func TestResolveSources_MissingArazzoFactory(t *testing.T) {
1935+
doc := &high.Arazzo{
1936+
SourceDescriptions: []*high.SourceDescription{
1937+
{Name: "flows", URL: "https://example.com/flows.yaml", Type: "arazzo"},
1938+
},
1939+
}
1940+
config := &ResolveConfig{
1941+
HTTPHandler: func(_ string) ([]byte, error) {
1942+
return []byte("content"), nil
1943+
},
1944+
}
1945+
_, err := ResolveSources(doc, config)
19551946
require.Error(t, err)
19561947
assert.Contains(t, err.Error(), "no ArazzoFactory configured")
19571948
}
19581949

1959-
func TestFactoryForType_UnknownType(t *testing.T) {
1960-
config := &ResolveConfig{}
1961-
_, err := factoryForType("graphql", config)
1950+
func TestResolveSources_UnknownSourceType_Coverage(t *testing.T) {
1951+
doc := &high.Arazzo{
1952+
SourceDescriptions: []*high.SourceDescription{
1953+
{Name: "api", URL: "https://example.com/api.graphql", Type: "graphql"},
1954+
},
1955+
}
1956+
config := &ResolveConfig{
1957+
HTTPHandler: func(_ string) ([]byte, error) {
1958+
return []byte("content"), nil
1959+
},
1960+
}
1961+
_, err := ResolveSources(doc, config)
19621962
require.Error(t, err)
19631963
assert.Contains(t, err.Error(), "unknown source type")
19641964
}
@@ -1999,15 +1999,16 @@ func TestResolveSources_HTTPTest_Integration(t *testing.T) {
19991999
{Name: "api", URL: server.URL + "/api.yaml", Type: "openapi"},
20002000
},
20012001
}
2002+
openAPIDoc := &v3high.Document{}
20022003
config := &ResolveConfig{
2003-
OpenAPIFactory: func(u string, b []byte) (any, error) {
2004-
return string(b), nil
2004+
OpenAPIFactory: func(u string, b []byte) (*v3high.Document, error) {
2005+
return openAPIDoc, nil
20052006
},
20062007
}
20072008
resolved, err := ResolveSources(doc, config)
20082009
require.NoError(t, err)
20092010
require.Len(t, resolved, 1)
2010-
assert.Equal(t, "openapi: 3.1.0", resolved[0].Document)
2011+
assert.Same(t, openAPIDoc, resolved[0].OpenAPIDocument)
20112012
}
20122013

20132014
func TestResolveSources_FileSource_Integration(t *testing.T) {
@@ -2020,15 +2021,16 @@ func TestResolveSources_FileSource_Integration(t *testing.T) {
20202021
{Name: "local", URL: filePath, Type: "openapi"},
20212022
},
20222023
}
2024+
openAPIDoc := &v3high.Document{}
20232025
config := &ResolveConfig{
2024-
OpenAPIFactory: func(u string, b []byte) (any, error) {
2025-
return string(b), nil
2026+
OpenAPIFactory: func(u string, b []byte) (*v3high.Document, error) {
2027+
return openAPIDoc, nil
20262028
},
20272029
}
20282030
resolved, err := ResolveSources(doc, config)
20292031
require.NoError(t, err)
20302032
require.Len(t, resolved, 1)
2031-
assert.Equal(t, "openapi: 3.1.0", resolved[0].Document)
2033+
assert.Same(t, openAPIDoc, resolved[0].OpenAPIDocument)
20322034
}
20332035

20342036
func TestResolveSources_URLValidationFails(t *testing.T) {

arazzo/engine_coverage_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/pb33f/libopenapi/arazzo/expression"
1919
high "github.com/pb33f/libopenapi/datamodel/high/arazzo"
20+
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
2021
"github.com/pb33f/libopenapi/orderedmap"
2122
"github.com/stretchr/testify/assert"
2223
"github.com/stretchr/testify/require"
@@ -1648,15 +1649,15 @@ func TestResolveSources_ArazzoType(t *testing.T) {
16481649
HTTPHandler: func(_ string) ([]byte, error) {
16491650
return []byte("content"), nil
16501651
},
1651-
ArazzoFactory: func(u string, b []byte) (any, error) {
1652-
return "arazzo-doc", nil
1652+
ArazzoFactory: func(u string, b []byte) (*high.Arazzo, error) {
1653+
return &high.Arazzo{}, nil
16531654
},
16541655
}
16551656
resolved, err := ResolveSources(doc, config)
16561657
require.NoError(t, err)
16571658
require.Len(t, resolved, 1)
16581659
assert.Equal(t, "arazzo", resolved[0].Type)
1659-
assert.Equal(t, "arazzo-doc", resolved[0].Document)
1660+
assert.NotNil(t, resolved[0].ArazzoDocument)
16601661
}
16611662

16621663
// ===========================================================================
@@ -2141,7 +2142,7 @@ func TestResolveFilePath_AbsSymlinkEscapeBlocked(t *testing.T) {
21412142
}
21422143

21432144
// ===========================================================================
2144-
// resolve.go: ResolveSources - factoryForType error (unknown type)
2145+
// resolve.go: ResolveSources - unknown source type
21452146
// ===========================================================================
21462147

21472148
func TestResolveSources_UnknownSourceType(t *testing.T) {
@@ -2437,14 +2438,14 @@ func TestResolveSources_FileSchemeSuccess(t *testing.T) {
24372438
}
24382439
config := &ResolveConfig{
24392440
FSRoots: []string{tmpDir},
2440-
OpenAPIFactory: func(u string, b []byte) (any, error) {
2441-
return "parsed-doc", nil
2441+
OpenAPIFactory: func(u string, b []byte) (*v3high.Document, error) {
2442+
return &v3high.Document{}, nil
24422443
},
24432444
}
24442445
resolved, err := ResolveSources(doc, config)
24452446
require.NoError(t, err)
24462447
require.Len(t, resolved, 1)
2447-
assert.Equal(t, "parsed-doc", resolved[0].Document)
2448+
assert.NotNil(t, resolved[0].OpenAPIDocument)
24482449
assert.Equal(t, "api", resolved[0].Name)
24492450
}
24502451

arazzo/final_coverage_test.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,21 +1004,31 @@ func TestValidateSourceURL_DisallowedHost(t *testing.T) {
10041004
}
10051005

10061006
// ---------------------------------------------------------------------------
1007-
// resolve.go: factoryForType
1007+
// resolve.go: ResolveSources - nil factory errors
10081008
// ---------------------------------------------------------------------------
10091009

1010-
func TestFactoryForType_Unknown(t *testing.T) {
1011-
_, err := factoryForType("graphql", &ResolveConfig{})
1012-
assert.Error(t, err)
1013-
assert.Contains(t, err.Error(), "unknown source type")
1014-
}
1015-
1016-
func TestFactoryForType_NilFactory(t *testing.T) {
1017-
_, err := factoryForType("openapi", &ResolveConfig{})
1010+
func TestResolveSources_NilOpenAPIFactory(t *testing.T) {
1011+
doc := &high.Arazzo{
1012+
SourceDescriptions: []*high.SourceDescription{
1013+
{Name: "api", URL: "https://example.com/api.yaml", Type: "openapi"},
1014+
},
1015+
}
1016+
_, err := ResolveSources(doc, &ResolveConfig{
1017+
HTTPHandler: func(_ string) ([]byte, error) { return []byte("ok"), nil },
1018+
})
10181019
assert.Error(t, err)
10191020
assert.Contains(t, err.Error(), "no OpenAPIFactory")
1021+
}
10201022

1021-
_, err = factoryForType("arazzo", &ResolveConfig{})
1023+
func TestResolveSources_NilArazzoFactory(t *testing.T) {
1024+
doc := &high.Arazzo{
1025+
SourceDescriptions: []*high.SourceDescription{
1026+
{Name: "flows", URL: "https://example.com/flows.yaml", Type: "arazzo"},
1027+
},
1028+
}
1029+
_, err := ResolveSources(doc, &ResolveConfig{
1030+
HTTPHandler: func(_ string) ([]byte, error) { return []byte("ok"), nil },
1031+
})
10221032
assert.Error(t, err)
10231033
assert.Contains(t, err.Error(), "no ArazzoFactory")
10241034
}

arazzo/resolve.go

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@ import (
1616
"time"
1717

1818
high "github.com/pb33f/libopenapi/datamodel/high/arazzo"
19+
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
1920
"github.com/pb33f/libopenapi/datamodel/low/arazzo"
2021
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
2122
)
2223

2324
var resolveFilepathAbs = filepath.Abs
2425

25-
// DocumentFactory is a function that creates a parsed document from raw bytes.
26+
// OpenAPIDocumentFactory creates a parsed OpenAPI document from raw bytes.
2627
// The sourceURL provides location context for relative reference resolution.
27-
type DocumentFactory func(sourceURL string, bytes []byte) (any, error)
28+
type OpenAPIDocumentFactory func(sourceURL string, bytes []byte) (*v3high.Document, error)
29+
30+
// ArazzoDocumentFactory creates a parsed Arazzo document from raw bytes.
31+
// The sourceURL provides location context for relative reference resolution.
32+
type ArazzoDocumentFactory func(sourceURL string, bytes []byte) (*high.Arazzo, error)
2833

2934
// ResolveConfig configures how source descriptions are resolved.
3035
type ResolveConfig struct {
31-
OpenAPIFactory DocumentFactory // Wraps libopenapi.NewDocument()
32-
ArazzoFactory DocumentFactory // Wraps libopenapi.NewArazzoDocument()
36+
OpenAPIFactory OpenAPIDocumentFactory // Creates *v3high.Document from bytes
37+
ArazzoFactory ArazzoDocumentFactory // Creates *high.Arazzo from bytes
3338
BaseURL string
3439
HTTPHandler func(url string) ([]byte, error)
3540
HTTPClient *http.Client
@@ -44,10 +49,11 @@ type ResolveConfig struct {
4449

4550
// ResolvedSource represents a successfully resolved source description.
4651
type ResolvedSource struct {
47-
Name string // SourceDescription name
48-
URL string // Resolved URL
49-
Type string // "openapi" or "arazzo"
50-
Document any // Resolved document (consumer type-asserts)
52+
Name string // SourceDescription name
53+
URL string // Resolved URL
54+
Type string // "openapi" or "arazzo"
55+
OpenAPIDocument *v3high.Document // Non-nil when Type == "openapi"
56+
ArazzoDocument *high.Arazzo // Non-nil when Type == "arazzo"
5157
}
5258

5359
// ResolveSources resolves all source descriptions in an Arazzo document.
@@ -109,19 +115,41 @@ func ResolveSources(doc *high.Arazzo, config *ResolveConfig) ([]*ResolvedSource,
109115
rs.Type = "openapi" // Default per spec
110116
}
111117

112-
factory, err := factoryForType(rs.Type, config)
113-
if err != nil {
114-
return nil, fmt.Errorf("%w (%q): %v", ErrSourceDescLoadFailed, sd.Name, err)
115-
}
116-
117-
rs.Document, err = factory(resolvedURL, docBytes)
118-
if err != nil {
119-
return nil, fmt.Errorf("%w (%q): %v", ErrSourceDescLoadFailed, sd.Name, err)
118+
switch rs.Type {
119+
case v3.OpenAPILabel:
120+
if config.OpenAPIFactory == nil {
121+
return nil, fmt.Errorf("%w (%q): no OpenAPIFactory configured", ErrSourceDescLoadFailed, sd.Name)
122+
}
123+
openDoc, factoryErr := config.OpenAPIFactory(resolvedURL, docBytes)
124+
if factoryErr != nil {
125+
return nil, fmt.Errorf("%w (%q): %v", ErrSourceDescLoadFailed, sd.Name, factoryErr)
126+
}
127+
rs.OpenAPIDocument = openDoc
128+
case arazzo.ArazzoLabel:
129+
if config.ArazzoFactory == nil {
130+
return nil, fmt.Errorf("%w (%q): no ArazzoFactory configured", ErrSourceDescLoadFailed, sd.Name)
131+
}
132+
arazzoDoc, factoryErr := config.ArazzoFactory(resolvedURL, docBytes)
133+
if factoryErr != nil {
134+
return nil, fmt.Errorf("%w (%q): %v", ErrSourceDescLoadFailed, sd.Name, factoryErr)
135+
}
136+
rs.ArazzoDocument = arazzoDoc
137+
default:
138+
return nil, fmt.Errorf("%w (%q): unknown source type %q", ErrSourceDescLoadFailed, sd.Name, rs.Type)
120139
}
121140

122141
resolved = append(resolved, rs)
123142
}
124143

144+
// Auto-attach OpenAPI source documents to the Arazzo model so that
145+
// validation and the engine can resolve operation references without
146+
// the caller needing to wire this up manually.
147+
for _, rs := range resolved {
148+
if rs.OpenAPIDocument != nil {
149+
doc.AddOpenAPISourceDocument(rs.OpenAPIDocument)
150+
}
151+
}
152+
125153
return resolved, nil
126154
}
127155

@@ -387,22 +415,6 @@ func ensureResolvedPathWithinRoots(path string, roots []string) error {
387415
return nil
388416
}
389417

390-
func factoryForType(sourceType string, config *ResolveConfig) (DocumentFactory, error) {
391-
switch sourceType {
392-
case v3.OpenAPILabel:
393-
if config.OpenAPIFactory == nil {
394-
return nil, fmt.Errorf("no OpenAPIFactory configured")
395-
}
396-
return config.OpenAPIFactory, nil
397-
case arazzo.ArazzoLabel:
398-
if config.ArazzoFactory == nil {
399-
return nil, fmt.Errorf("no ArazzoFactory configured")
400-
}
401-
return config.ArazzoFactory, nil
402-
default:
403-
return nil, fmt.Errorf("unknown source type %q", sourceType)
404-
}
405-
}
406418

407419
func containsFold(values []string, value string) bool {
408420
for _, v := range values {

0 commit comments

Comments
 (0)