Skip to content

Commit d5455c6

Browse files
committed
Tuned up tests and logic for #90
seems to be working quite well.!
1 parent 3eed821 commit d5455c6

File tree

13 files changed

+760
-92
lines changed

13 files changed

+760
-92
lines changed

datamodel/high/base/schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ func (s *Schema) RenderInline() ([]byte, error) {
491491
return yaml.Marshal(d)
492492
}
493493

494-
// MarshalYAML will create a ready to render YAML representation of the ExternalDoc object.
494+
// MarshalYAML will create a ready to render YAML representation of the Schema object.
495495
func (s *Schema) MarshalYAML() (interface{}, error) {
496496
nb := high.NewNodeBuilder(s, s.low)
497497

datamodel/low/base/schema.go

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -640,19 +640,8 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
640640
root = utils.NodeAlias(root)
641641
utils.CheckForMergeNodes(root)
642642

643-
// transform sibling refs to allOf structure if enabled and applicable
644-
if idx != nil && idx.GetConfig() != nil && idx.GetConfig().TransformSiblingRefs {
645-
transformer := NewSiblingRefTransformer(idx)
646-
if transformer.ShouldTransform(root) {
647-
transformed, err := transformer.TransformSiblingRef(root)
648-
if err != nil {
649-
return fmt.Errorf("sibling ref transformation failed: %w", err)
650-
}
651-
if transformed != nil {
652-
root = transformed
653-
}
654-
}
655-
}
643+
// Note: sibling ref transformation now happens in SchemaProxy.Build()
644+
// so the root node should already be pre-transformed if needed
656645

657646
s.Reference = new(low.Reference)
658647
no := low.ExtractNodes(ctx, root)
@@ -662,21 +651,30 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
662651
s.context = ctx
663652
s.index = idx
664653

665-
if h, _, _ := utils.IsNodeRefValue(root); h {
666-
ref, _, err, fctx := low.LocateRefNodeWithContext(ctx, root, idx)
667-
if ref != nil {
668-
root = ref
669-
if fctx != nil {
670-
ctx = fctx
671-
}
672-
if err != nil {
673-
if !idx.AllowCircularReferenceResolving() {
674-
return fmt.Errorf("build schema failed: %s", err.Error())
654+
// check if this schema was transformed from a sibling ref
655+
// if so, skip reference dereferencing to preserve the allOf structure
656+
isTransformed := false
657+
if s.ParentProxy != nil && s.ParentProxy.TransformedRef != nil {
658+
isTransformed = true
659+
}
660+
661+
if !isTransformed {
662+
if h, _, _ := utils.IsNodeRefValue(root); h {
663+
ref, _, err, fctx := low.LocateRefNodeWithContext(ctx, root, idx)
664+
if ref != nil {
665+
root = ref
666+
if fctx != nil {
667+
ctx = fctx
675668
}
669+
if err != nil {
670+
if !idx.AllowCircularReferenceResolving() {
671+
return fmt.Errorf("build schema failed: %s", err.Error())
672+
}
673+
}
674+
} else {
675+
return fmt.Errorf("build schema failed: reference cannot be found: '%s', line %d, col %d",
676+
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
676677
}
677-
} else {
678-
return fmt.Errorf("build schema failed: reference cannot be found: '%s', line %d, col %d",
679-
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
680678
}
681679
}
682680

@@ -1299,12 +1297,17 @@ func buildPropertyMap(ctx context.Context, parent *Schema, root *yaml.Node, idx
12991297
sp := &SchemaProxy{ctx: foundCtx, kn: currentProp, vn: prop, idx: foundIdx}
13001298
sp.SetReference(refString, refNode)
13011299

1300+
err := sp.Build(foundCtx, currentProp, prop, foundIdx)
1301+
if err != nil {
1302+
return nil, fmt.Errorf("schema proxy build failed: %w", err)
1303+
}
1304+
13021305
propertyMap.Set(low.KeyReference[string]{
13031306
KeyNode: currentProp,
13041307
Value: currentProp.Value,
13051308
}, low.ValueReference[*SchemaProxy]{
13061309
Value: sp,
1307-
ValueNode: prop,
1310+
ValueNode: sp.vn, // use transformed node
13081311
})
13091312
}
13101313

@@ -1384,12 +1387,56 @@ func (s *Schema) extractExtensions(root *yaml.Node) {
13841387
s.Extensions = low.ExtractExtensions(root)
13851388
}
13861389

1390+
// buildAllOfFromTransformedNode manually builds the AllOf field from a transformed allOf node structure
1391+
// This is used when transformation creates an allOf structure but BuildModel doesn't pick it up
1392+
func (s *Schema) buildAllOfFromTransformedNode(root *yaml.Node) error {
1393+
if len(root.Content) < 2 || root.Content[0].Value != "allOf" {
1394+
return fmt.Errorf("invalid allOf structure")
1395+
}
1396+
1397+
allOfArray := root.Content[1] // the array node containing allOf items
1398+
if allOfArray.Kind != yaml.SequenceNode {
1399+
return fmt.Errorf("allOf value is not an array")
1400+
}
1401+
1402+
var allOfSchemas []low.ValueReference[*SchemaProxy]
1403+
for _, item := range allOfArray.Content {
1404+
// create a schema proxy for each allOf item and build it
1405+
schemaProxy := &SchemaProxy{
1406+
vn: item,
1407+
idx: s.index,
1408+
ctx: s.context,
1409+
}
1410+
1411+
// build the schema proxy so it has proper content
1412+
err := schemaProxy.Build(s.context, nil, item, s.index)
1413+
if err != nil {
1414+
return fmt.Errorf("failed to build schema proxy for allOf item: %w", err)
1415+
}
1416+
1417+
allOfSchemas = append(allOfSchemas, low.ValueReference[*SchemaProxy]{
1418+
Value: schemaProxy,
1419+
ValueNode: item,
1420+
})
1421+
}
1422+
1423+
// set the AllOf field
1424+
s.AllOf = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
1425+
Value: allOfSchemas,
1426+
KeyNode: root.Content[0], // "allOf" key
1427+
ValueNode: root.Content[1], // array value
1428+
}
1429+
1430+
return nil
1431+
}
1432+
13871433
// build out a child schema for parent schema.
13881434
func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml.Node, errors chan error, idx *index.SpecIndex) {
13891435
if valueNode != nil {
13901436
type buildResult struct {
13911437
res *low.ValueReference[*SchemaProxy]
13921438
idx int
1439+
err error
13931440
}
13941441

13951442
syncChan := make(chan buildResult)
@@ -1405,16 +1452,19 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
14051452
// In order to combat this, we need a schema proxy that will only resolve the schema when asked, and then
14061453
// it will only do it one level at a time.
14071454
sp := new(SchemaProxy)
1408-
sp.kn = kn
1409-
sp.vn = vn
1410-
sp.idx = fIdx
1411-
sp.ctx = pctx
1455+
1456+
// call Build to ensure transformation happens
1457+
err := sp.Build(pctx, kn, vn, fIdx)
1458+
if err != nil {
1459+
return buildResult{err: err}
1460+
}
1461+
14121462
if isRef {
14131463
sp.SetReference(refLocation, rf)
14141464
}
14151465
res := &low.ValueReference[*SchemaProxy]{
14161466
Value: sp,
1417-
ValueNode: vn,
1467+
ValueNode: sp.vn, // use transformed node
14181468
}
14191469
return buildResult{
14201470
res: res,
@@ -1446,6 +1496,10 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
14461496
// this only runs once, however to keep things consistent, it makes sense to use the same async method
14471497
// that arrays will use.
14481498
r := build(foundCtx, foundIdx, labelNode, valueNode, refNode, -1, syncChan, isRef, refLocation)
1499+
if r.err != nil {
1500+
errors <- r.err
1501+
return
1502+
}
14491503
schemas <- schemaProxyBuildResult{
14501504
k: low.KeyReference[string]{
14511505
KeyNode: labelNode,
@@ -1479,6 +1533,10 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
14791533
}
14801534
refBuilds++
14811535
r := build(foundCtx, foundIdx, vn, vn, refNode, i, syncChan, isRef, refLocation)
1536+
if r.err != nil {
1537+
errors <- r.err
1538+
return
1539+
}
14821540
results[r.idx] = r.res
14831541
}
14841542

@@ -1554,12 +1612,19 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
15541612
if schNode != nil {
15551613
// check if schema has already been built.
15561614
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: foundIndex, ctx: foundCtx}
1615+
1616+
// call Build to ensure transformation happens
1617+
err := schema.Build(foundCtx, schLabel, schNode, foundIndex)
1618+
if err != nil {
1619+
return nil, fmt.Errorf("schema proxy build failed in ExtractSchema: %w", err)
1620+
}
1621+
15571622
schema.SetReference(refLocation, refNode)
15581623

15591624
n := &low.NodeReference[*SchemaProxy]{
15601625
Value: schema,
15611626
KeyNode: schLabel,
1562-
ValueNode: schNode,
1627+
ValueNode: schema.vn, // use transformed node
15631628
}
15641629
n.SetReference(refLocation, refNode)
15651630
return n, nil

datamodel/low/base/schema_proxy.go

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,54 @@ import (
5252
// when a schema is needed, so the rest of the document is parsed and ready to use.
5353
type SchemaProxy struct {
5454
low.Reference
55-
kn *yaml.Node
56-
vn *yaml.Node
57-
idx *index.SpecIndex
58-
rendered *Schema
59-
buildError error
60-
ctx context.Context
61-
cachedHash *[32]byte // Cache computed hash to avoid recalculation
55+
kn *yaml.Node
56+
vn *yaml.Node
57+
idx *index.SpecIndex
58+
rendered *Schema
59+
buildError error
60+
ctx context.Context
61+
cachedHash *[32]byte // Cache computed hash to avoid recalculation
62+
TransformedRef *yaml.Node // Original node that contained the ref before transformation
6263
*low.NodeMap
6364
}
6465

6566
// Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state.
6667
// Key maybe nil if absent.
6768
func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex) error {
6869
sp.kn = key
69-
sp.vn = value
7070
sp.idx = idx
7171
sp.ctx = ctx
72-
if rf, _, r := utils.IsNodeRefValue(value); rf {
73-
sp.SetReference(r, value)
72+
73+
// transform sibling refs to allOf structure if enabled and applicable
74+
// this ensures sp.vn contains the pre-transformed YAML as the source of truth
75+
transformedValue := value
76+
wasTransformed := false
77+
if idx != nil && idx.GetConfig() != nil && idx.GetConfig().TransformSiblingRefs {
78+
transformer := NewSiblingRefTransformer(idx)
79+
if transformer.ShouldTransform(value) {
80+
transformed, err := transformer.TransformSiblingRef(value)
81+
if err != nil {
82+
return fmt.Errorf("sibling ref transformation failed: %w", err)
83+
}
84+
if transformed != nil {
85+
transformedValue = transformed
86+
wasTransformed = true
87+
sp.TransformedRef = value // store original node that had the ref
88+
}
89+
}
90+
}
91+
92+
sp.vn = transformedValue
93+
94+
// handle reference detection
95+
if !wasTransformed {
96+
// for non-transformed schemas, handle reference normally
97+
if rf, _, r := utils.IsNodeRefValue(transformedValue); rf {
98+
sp.SetReference(r, transformedValue)
99+
}
74100
}
101+
// for transformed schemas, don't set reference since it's now an allOf structure
102+
// the reference is embedded within the allOf, but the schema itself is not a pure reference
75103
var m sync.Map
76104
sp.NodeMap = &low.NodeMap{Nodes: &m}
77105
return nil

0 commit comments

Comments
 (0)