Added new extraction functions to control extensions

New extraction functions added (that just wrap the old ones). The difference here is that the extensions can be included or ignored now. This has been added to address issue #24 where header keys can in fact include an extension prefix.

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-05-17 09:33:52 -04:00
parent 08f596058f
commit 230f8fdd02
5 changed files with 171 additions and 15 deletions

View File

@@ -354,14 +354,15 @@ func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
return ref return ref
} }
// ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part // ExtractMapNoLookupExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
// refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the // refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the
// root yaml.Node pointer is used directly. // root yaml.Node pointer is used directly. Pass a true bit to includeExtensions to include extension keys in the map.
// //
// This is useful when the node to be extracted, is already known and does not require a search. // This is useful when the node to be extracted, is already known and does not require a search.
func ExtractMapNoLookup[PT Buildable[N], N any]( func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
includeExtensions bool,
) (map[KeyReference[string]]ValueReference[PT], error) { ) (map[KeyReference[string]]ValueReference[PT], error) {
valueMap := make(map[KeyReference[string]]ValueReference[PT]) valueMap := make(map[KeyReference[string]]ValueReference[PT])
var circError error var circError error
@@ -369,9 +370,11 @@ func ExtractMapNoLookup[PT Buildable[N], N any](
var currentKey *yaml.Node var currentKey *yaml.Node
skip := false skip := false
for i, node := range root.Content { for i, node := range root.Content {
if strings.HasPrefix(strings.ToLower(node.Value), "x-") { if !includeExtensions {
skip = true if strings.HasPrefix(strings.ToLower(node.Value), "x-") {
continue skip = true
continue
}
} }
if skip { if skip {
skip = false skip = false
@@ -430,6 +433,19 @@ func ExtractMapNoLookup[PT Buildable[N], N any](
return valueMap, circError return valueMap, circError
} }
return valueMap, nil return valueMap, nil
}
// ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
// refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the
// root yaml.Node pointer is used directly.
//
// This is useful when the node to be extracted, is already known and does not require a search.
func ExtractMapNoLookup[PT Buildable[N], N any](
root *yaml.Node,
idx *index.SpecIndex,
) (map[KeyReference[string]]ValueReference[PT], error) {
return ExtractMapNoLookupExtensions[PT, N](root, idx, false)
} }
type mappingResult[T any] struct { type mappingResult[T any] struct {
@@ -437,15 +453,17 @@ type mappingResult[T any] struct {
v ValueReference[T] v ValueReference[T]
} }
// ExtractMap will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is // ExtractMapExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is
// used to locate the node to be extracted from the root node supplied. // used to locate the node to be extracted from the root node supplied. Supply a bit to decide if extensions should
// be included or not. required in some use cases.
// //
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node // The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node. // found for the value extracted from the label node.
func ExtractMap[PT Buildable[N], N any]( func ExtractMapExtensions[PT Buildable[N], N any](
label string, label string,
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
extensions bool,
) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { ) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
//var isReference bool //var isReference bool
var referenceValue string var referenceValue string
@@ -547,8 +565,10 @@ func ExtractMap[PT Buildable[N], N any](
} }
} }
if strings.HasPrefix(currentLabelNode.Value, "x-") { if !extensions {
continue // yo, don't pay any attention to extensions, not here anyway. if strings.HasPrefix(currentLabelNode.Value, "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
} }
totalKeys++ totalKeys++
go buildMap(currentLabelNode, en, bChan, eChan, referenceValue) go buildMap(currentLabelNode, en, bChan, eChan, referenceValue)
@@ -572,6 +592,19 @@ func ExtractMap[PT Buildable[N], N any](
return nil, labelNode, valueNode, nil return nil, labelNode, valueNode, nil
} }
// ExtractMap will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is
// used to locate the node to be extracted from the root node supplied.
//
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMap[PT Buildable[N], N any](
label string,
root *yaml.Node,
idx *index.SpecIndex,
) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
return ExtractMapExtensions[PT, N](label, root, idx, false)
}
// ExtractExtensions will extract any 'x-' prefixed key nodes from a root node into a map. Requirements have been pre-cast: // ExtractExtensions will extract any 'x-' prefixed key nodes from a root node into a map. Requirements have been pre-cast:
// //
// Maps // Maps

View File

@@ -973,6 +973,106 @@ one:
} }
func TestExtractMap_NoLookupWithExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `x-hey: you
one:
x-choo: choo`
var cNode yaml.Node
e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e)
things, err := ExtractMapNoLookupExtensions[*test_Good](cNode.Content[0], idx, true)
assert.NoError(t, err)
assert.Len(t, things, 2)
for k, v := range things {
if k.Value == "x-hey" {
continue
}
assert.Equal(t, "one", k.Value)
assert.Len(t, v.ValueNode.Content, 2)
}
}
func TestExtractMap_NoLookupWithoutExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `x-hey: you
one:
x-choo: choo`
var cNode yaml.Node
e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e)
things, err := ExtractMapNoLookupExtensions[*test_Good](cNode.Content[0], idx, false)
assert.NoError(t, err)
assert.Len(t, things, 1)
for k, _ := range things {
assert.Equal(t, "one", k.Value)
}
}
func TestExtractMap_WithExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `x-hey: you
one:
x-choo: choo`
var cNode yaml.Node
e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e)
things, _, _, err := ExtractMapExtensions[*test_Good]("one", cNode.Content[0], idx, true)
assert.NoError(t, err)
assert.Len(t, things, 1)
}
func TestExtractMap_WithoutExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `x-hey: you
one:
x-choo: choo`
var cNode yaml.Node
e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e)
things, _, _, err := ExtractMapExtensions[*test_Good]("one", cNode.Content[0], idx, false)
assert.NoError(t, err)
assert.Len(t, things, 0)
}
func TestExtractMapFlatNoLookup_Ref(t *testing.T) { func TestExtractMapFlatNoLookup_Ref(t *testing.T) {
yml := `components: yml := `components:

View File

@@ -17,7 +17,7 @@ import (
// //
// Describes a single response from an API Operation, including design-time, static links to // Describes a single response from an API Operation, including design-time, static links to
// operations based on the response. // operations based on the response.
// - https://spec.openapis.org/oas/v3.1.0#response-object // - https://spec.openapis.org/oas/v3.1.0#response-object
type Response struct { type Response struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]
@@ -58,7 +58,7 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
//extract headers //extract headers
headers, lN, kN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) headers, lN, kN, err := low.ExtractMapExtensions[*Header](HeadersLabel, root, idx, true)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -64,7 +64,6 @@ default:
assert.Equal(t, "c009b2046101bc03df802b4cf23f78176931137e6115bf7b445ca46856c06b51", assert.Equal(t, "c009b2046101bc03df802b4cf23f78176931137e6115bf7b445ca46856c06b51",
low.GenerateHashString(&n)) low.GenerateHashString(&n))
} }
func TestResponses_NoDefault(t *testing.T) { func TestResponses_NoDefault(t *testing.T) {
@@ -217,6 +216,30 @@ func TestResponses_Build_FailBadLinks(t *testing.T) {
} }
func TestResponses_Build_AllowXPrefixHeader(t *testing.T) {
yml := `"200":
headers:
x-header1:
schema:
type: string`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode)
var n Responses
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "string",
n.FindResponseByCode("200").Value.FindHeader("x-header1").Value.Schema.Value.Schema().Type.Value.A)
}
func TestResponse_Hash(t *testing.T) { func TestResponse_Hash(t *testing.T) {
yml := `description: nice toast yml := `description: nice toast

View File

@@ -27,7 +27,7 @@ import (
// //
// The Responses Object MUST contain at least one response code, and if only one response code is provided it SHOULD // The Responses Object MUST contain at least one response code, and if only one response code is provided it SHOULD
// be the response for a successful operation call. // be the response for a successful operation call.
// - https://spec.openapis.org/oas/v3.1.0#responses-object // - https://spec.openapis.org/oas/v3.1.0#responses-object
// //
// This structure is identical to the v2 version, however they use different response types, hence // This structure is identical to the v2 version, however they use different response types, hence
// the duplication. Perhaps in the future we could use generics here, but for now to keep things // the duplication. Perhaps in the future we could use generics here, but for now to keep things