Skip to content

Commit a79c82a

Browse files
committed
Fix detecting @field annotations outside YAML nodes and support emptyobject type
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent ea5ca19 commit a79c82a

2 files changed

Lines changed: 115 additions & 3 deletions

File tree

internal/readme/readme.go

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,43 @@ func parseMetadataComments(path string) (*Meta, error) {
162162
// build param-name → type alias map
163163
aliasMap := buildAliasMap(allParams)
164164

165+
seen := map[fieldKey]struct{}{}
166+
addField := func(parent, name, typ, desc string) {
167+
if parent == "" || name == "" {
168+
return
169+
}
170+
if real, ok := aliasMap[parent]; ok {
171+
parent = real
172+
}
173+
k := fieldKey{parent: parent, name: name}
174+
if _, dup := seen[k]; dup {
175+
return
176+
}
177+
typeFields[parent] = append(typeFields[parent], FieldMeta{
178+
ParentTypeName: parent,
179+
Name: name,
180+
Type: typ,
181+
Description: desc,
182+
})
183+
seen[k] = struct{}{}
184+
}
185+
186+
// collect from raw lines
187+
for _, line := range lines {
188+
if m := fieldRe.FindStringSubmatch(strings.TrimSpace(line)); m != nil {
189+
qual := m[1]
190+
typ := m[2]
191+
desc := m[3]
192+
193+
rootType, field := qual, ""
194+
if strings.Contains(qual, ".") {
195+
parts := strings.SplitN(qual, ".", 2)
196+
rootType, field = parts[0], parts[1]
197+
}
198+
addField(rootType, field, typ, desc)
199+
}
200+
}
201+
165202
// ───────────── second pass: traverse YAML & @field ─────────────
166203
var walk func(node *yaml.Node, path []string)
167204
walk = func(node *yaml.Node, path []string) {
@@ -406,9 +443,22 @@ func normalizeType(t string) string {
406443
t = t[:idx]
407444
}
408445

409-
// if underlying base is primitive, preserve it verbatim
446+
// normalize emptyobject for display
447+
if t == aliasEmptyObject {
448+
return "object"
449+
}
450+
if t == "*"+aliasEmptyObject {
451+
return "*object"
452+
}
453+
454+
// if underlying base is primitive, preserve it verbatim (after emptyobject normalization above)
410455
base := deriveTypeName(t)
411456
if isPrimitive(base) {
457+
// show emptyobject as object in composite forms too
458+
if base == aliasEmptyObject {
459+
// handle map/array already below; for bare it's handled above
460+
return strings.ReplaceAll(t, aliasEmptyObject, "object")
461+
}
412462
return t
413463
}
414464

@@ -418,11 +468,19 @@ func normalizeType(t string) string {
418468
if !isPrimitive(base) {
419469
return "[]object"
420470
}
471+
// []emptyobject -> []object
472+
if base == aliasEmptyObject {
473+
return "[]object"
474+
}
421475
return "[]" + base
422476
}
423477

424478
// Handle map types
425479
if strings.HasPrefix(t, "map[") {
480+
// map[string]emptyobject -> map[string]object
481+
if deriveTypeName(t) == aliasEmptyObject {
482+
return "map[string]object"
483+
}
426484
return "map[string]object"
427485
}
428486

@@ -431,13 +489,13 @@ func normalizeType(t string) string {
431489
if !isPrimitive(base) && !strings.HasPrefix(base, "[]") && !strings.HasPrefix(base, "map[") {
432490
return "*object"
433491
}
492+
// *emptyobject already handled above
434493
return t
435494
}
436495

437496
if !isPrimitive(t) && !strings.HasPrefix(t, "[]") && !strings.HasPrefix(t, "map[") {
438497
return "object"
439498
}
440-
441499
return t
442500
}
443501

@@ -485,6 +543,11 @@ func valueString(raw interface{}, exists bool, t string) string {
485543
}
486544
}
487545

546+
type fieldKey struct {
547+
parent string
548+
name string
549+
}
550+
488551
func isPrimitive(t string) bool {
489552
base := strings.TrimPrefix(t, "*")
490553
if isStringFormat(base) {
@@ -493,7 +556,7 @@ func isPrimitive(t string) bool {
493556
switch base {
494557
case "string", "bool", "int", "int32", "int64",
495558
"float32", "float64",
496-
aliasQuantity, aliasDuration, aliasTime, aliasObject:
559+
aliasQuantity, aliasDuration, aliasTime, aliasObject, aliasEmptyObject:
497560
return true
498561
default:
499562
return false

internal/readme/readme_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,52 @@ config:
582582

583583
require.NoError(t, validateValues(params, typeFields, vals))
584584
}
585+
586+
func TestSourceUploadSchemaFromTopBlock(t *testing.T) {
587+
yamlContent := `
588+
## @section Common parameters
589+
## @param source {source} The source image location used to create a disk
590+
## @field source.image {*uploadImage} Use image by name
591+
## @field uploadImage.name {string} Name of the image to use
592+
## @field source.upload {*emptyobject} Upload local image
593+
## @field source.http {*uploadHTTP} Download image from an HTTP source
594+
## @field uploadHTTP.url {string} URL to download the image
595+
596+
source: {}
597+
598+
## @param optical {bool} Defines if disk should be considered optical
599+
optical: false
600+
601+
## @param storage {quantity} The size of the disk allocated for the virtual machine
602+
## @param storageClass {string} StorageClass used to store the data
603+
storage: 5Gi
604+
storageClass: replicated
605+
`
606+
path := writeTempFile(t, yamlContent)
607+
defer os.Remove(path)
608+
609+
vals, err := createValuesObject(path)
610+
require.NoError(t, err)
611+
612+
meta, err := parseMetadataComments(path)
613+
require.NoError(t, err)
614+
615+
var params []ParamMeta
616+
for _, s := range meta.Sections {
617+
params = append(params, s.Parameters...)
618+
}
619+
620+
// validate should pass: 'source' type schema is known
621+
require.NoError(t, validateValues(params, typeFields, vals))
622+
623+
// rendered table should show object for emptyobject and nested fields
624+
table := renderTableFromValues(t, yamlContent)
625+
require.Contains(t, table, "`source`")
626+
require.Contains(t, table, "`{}`")
627+
require.Contains(t, table, "`source.image`")
628+
require.Contains(t, table, "`source.upload`") // emptyobject displayed as object
629+
require.Contains(t, table, "`source.http`")
630+
// normalizeType for emptyobject
631+
require.NotContains(t, table, "`emptyobject`")
632+
require.Contains(t, table, "`object`")
633+
}

0 commit comments

Comments
 (0)