fix: More ducmentation + tests

This commit is contained in:
Ilya Kuznetsov 2025-01-15 22:10:39 +01:00
parent d27282c3a4
commit 13181fc5a7
No known key found for this signature in database
GPG Key ID: 91F3DDCF5D21CDDF
4 changed files with 185 additions and 29 deletions

View File

@ -87,13 +87,13 @@ func buildAttributeTable(m *markdownRenderer, attributes []attributeNode) *markd
func formatDescription(a attributeNode) string { func formatDescription(a attributeNode) string {
s := strings.ReplaceAll(a.Description, "\n", " ") s := strings.ReplaceAll(a.Description, "\n", " ")
if a.Reference != "" { if a.Link != "" {
if strings.HasSuffix(s, ".") { if strings.HasSuffix(s, ".") {
s += " " s += " "
} else if s != "" { } else if s != "" {
s += ". " s += ". "
} }
s += fmt.Sprintf("See [_](#%s).", a.Reference) s += fmt.Sprintf("See [_](#%s).", a.Link)
} }
return s return s
} }

View File

@ -24,7 +24,7 @@ type attributeNode struct {
Title string Title string
Type string Type string
Description string Description string
Reference string Link string
} }
type rootProp struct { type rootProp struct {
@ -61,6 +61,7 @@ func buildNodes(s jsonschema.Schema, refs map[string]*jsonschema.Schema, ownFiel
continue continue
} }
visited[k] = true visited[k] = true
v = resolveRefs(v, refs) v = resolveRefs(v, refs)
node := rootNode{ node := rootNode{
Title: k, Title: k,
@ -70,37 +71,56 @@ func buildNodes(s jsonschema.Schema, refs map[string]*jsonschema.Schema, ownFiel
Type: getHumanReadableType(v.Type), Type: getHumanReadableType(v.Type),
} }
hasProperties := len(v.Properties) > 0
if hasProperties {
node.Attributes = getAttributes(v.Properties, refs, ownFields, k, item.circular) node.Attributes = getAttributes(v.Properties, refs, ownFields, k, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(k, v.Properties, refs, ownFields)...)
} }
additionalProps, ok := v.AdditionalProperties.(*jsonschema.Schema) mapValueType := getMapValueType(v, refs)
if ok { if mapValueType != nil {
objectKeyType := resolveRefs(additionalProps, refs) d := getDescription(mapValueType, true)
d := getDescription(objectKeyType, true)
if d != "" { if d != "" {
node.Description = d node.Description = d
} }
if len(node.Example) == 0 { if node.Example == "" {
node.Example = getExample(objectKeyType) node.Example = getExample(mapValueType)
}
prefix := k + ".<name>"
node.ObjectKeyAttributes = getAttributes(objectKeyType.Properties, refs, ownFields, prefix, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(prefix, objectKeyType.Properties, refs, ownFields)...)
} }
node.ObjectKeyAttributes = getAttributes(mapValueType.Properties, refs, ownFields, getMapKeyPrefix(k), item.circular)
} }
if v.Items != nil {
arrayItemType := resolveRefs(v.Items, refs) arrayItemType := resolveRefs(v.Items, refs)
if arrayItemType != nil {
node.ArrayItemAttributes = getAttributes(arrayItemType.Properties, refs, ownFields, k, item.circular) node.ArrayItemAttributes = getAttributes(arrayItemType.Properties, refs, ownFields, k, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(k, arrayItemType.Properties, refs, ownFields)...)
}
} }
nodes = append(nodes, node) nodes = append(nodes, node)
// Whether we should add new root props from the children of the current JSON-schema node to include their definitions to this document
shouldAddNewProps := !item.circular
if shouldAddNewProps {
newProps := []rootProp{}
// Adds node with definition for the properties. Example:
// bundle:
// prop-name: <value>
if hasProperties {
newProps = append(newProps, extractNodes(k, v.Properties, refs, ownFields)...)
}
// Adds node with definition for the type of array item. Example:
// permissions:
// - <item>
if arrayItemType != nil {
newProps = append(newProps, extractNodes(k, arrayItemType.Properties, refs, ownFields)...)
}
// Adds node with definition for the type of the Map value. Example:
// targets:
// <key>: <value>
if mapValueType != nil {
newProps = append(newProps, extractNodes(getMapKeyPrefix(k), mapValueType.Properties, refs, ownFields)...)
}
rootProps = append(rootProps, newProps...)
}
} }
sort.Slice(nodes, func(i, j int) bool { sort.Slice(nodes, func(i, j int) bool {
@ -109,6 +129,18 @@ func buildNodes(s jsonschema.Schema, refs map[string]*jsonschema.Schema, ownFiel
return nodes return nodes
} }
func getMapValueType(v *jsonschema.Schema, refs map[string]*jsonschema.Schema) *jsonschema.Schema {
additionalProps, ok := v.AdditionalProperties.(*jsonschema.Schema)
if ok {
return resolveRefs(additionalProps, refs)
}
return nil
}
func getMapKeyPrefix(s string) string {
return s + ".<name>"
}
func removePluralForm(s string) string { func removePluralForm(s string) string {
if strings.HasSuffix(s, "s") { if strings.HasSuffix(s, "s") {
return strings.TrimSuffix(s, "s") return strings.TrimSuffix(s, "s")
@ -143,7 +175,7 @@ func getAttributes(props, refs map[string]*jsonschema.Schema, ownFields map[stri
Title: k, Title: k,
Type: typeString, Type: typeString,
Description: getDescription(v, true), Description: getDescription(v, true),
Reference: reference, Link: reference,
}) })
} }
sort.Slice(attributes, func(i, j int) bool { sort.Slice(attributes, func(i, j int) bool {
@ -172,7 +204,7 @@ func shouldExtract(ref string, ownFields map[string]bool) bool {
func extractNodes(prefix string, props, refs map[string]*jsonschema.Schema, ownFields map[string]bool) []rootProp { func extractNodes(prefix string, props, refs map[string]*jsonschema.Schema, ownFields map[string]bool) []rootProp {
nodes := []rootProp{} nodes := []rootProp{}
for k, v := range props { for k, v := range props {
if !shouldExtract(*v.Reference, ownFields) { if v.Reference != nil && !shouldExtract(*v.Reference, ownFields) {
continue continue
} }
v = resolveRefs(v, refs) v = resolveRefs(v, refs)

View File

@ -0,0 +1,120 @@
package main
import (
"testing"
"github.com/databricks/cli/libs/jsonschema"
"github.com/stretchr/testify/assert"
)
func TestBuildNodes_ChildExpansion(t *testing.T) {
tests := []struct {
name string
schema jsonschema.Schema
refs map[string]*jsonschema.Schema
ownFields map[string]bool
wantNodes []rootNode
}{
{
name: "array expansion",
schema: jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"list": {
Type: "array",
Items: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"listSub": {Reference: strPtr("#/$defs/github.com/listSub")},
},
},
},
},
},
refs: map[string]*jsonschema.Schema{
"github.com/listSub": {Type: "array", Items: &jsonschema.Schema{Type: "object", Properties: map[string]*jsonschema.Schema{"subField": {Type: "string"}}}},
},
ownFields: map[string]bool{"github.com/listSub": true},
wantNodes: []rootNode{
{
Title: "list",
TopLevel: true,
Type: "Sequence",
ArrayItemAttributes: []attributeNode{
{Title: "listSub", Type: "Sequence", Link: "list.listSub"},
},
},
{
Title: "list.listSub",
Type: "Sequence",
ArrayItemAttributes: []attributeNode{
{Title: "subField", Type: "String"},
},
},
},
},
{
name: "map expansion",
schema: jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"myMap": {
Type: "object",
AdditionalProperties: &jsonschema.Schema{
Reference: strPtr("#/$defs/github.com/myMap"),
Properties: map[string]*jsonschema.Schema{
"mapSub": {Type: "object", Reference: strPtr("#/$defs/github.com/mapSub")},
},
},
},
},
},
refs: map[string]*jsonschema.Schema{
"github.com/myMap": {
Type: "object",
Properties: map[string]*jsonschema.Schema{
"mapSub": {Type: "boolean", Reference: strPtr("#/$defs/github.com/mapSub")},
},
},
"github.com/mapSub": {
Type: "object",
Properties: map[string]*jsonschema.Schema{
"deepSub": {Type: "boolean"},
},
},
},
ownFields: map[string]bool{
"github.com/myMap": true,
"github.com/mapSub": true,
},
wantNodes: []rootNode{
{
Title: "myMap",
TopLevel: true,
Type: "Map",
ObjectKeyAttributes: []attributeNode{
{Title: "mapSub", Type: "Map", Link: "myMap.<name>.mapSub"},
},
},
{
Title: "myMap.<name>.mapSub",
Type: "Map",
Attributes: []attributeNode{
{Title: "deepSub", Type: "Boolean"},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := buildNodes(tt.schema, tt.refs, tt.ownFields)
assert.Equal(t, tt.wantNodes, got)
})
}
}
func strPtr(s string) *string {
return &s
}

View File

@ -7,7 +7,7 @@ import (
"github.com/databricks/cli/libs/jsonschema" "github.com/databricks/cli/libs/jsonschema"
) )
func isReferenceType(v *jsonschema.Schema, refs map[string]*jsonschema.Schema, customFields map[string]bool) bool { func isReferenceType(v *jsonschema.Schema, refs map[string]*jsonschema.Schema, ownFields map[string]bool) bool {
if v.Type != "object" && v.Type != "array" { if v.Type != "object" && v.Type != "array" {
return false return false
} }
@ -21,7 +21,7 @@ func isReferenceType(v *jsonschema.Schema, refs map[string]*jsonschema.Schema, c
} }
} }
props := resolveAdditionalProperties(v) props := resolveAdditionalProperties(v)
if !isInOwnFields(props, customFields) { if !isInOwnFields(props, ownFields) {
return false return false
} }
if props != nil { if props != nil {
@ -32,9 +32,9 @@ func isReferenceType(v *jsonschema.Schema, refs map[string]*jsonschema.Schema, c
return false return false
} }
func isInOwnFields(node *jsonschema.Schema, customFields map[string]bool) bool { func isInOwnFields(node *jsonschema.Schema, ownFields map[string]bool) bool {
if node != nil && node.Reference != nil { if node != nil && node.Reference != nil {
return customFields[getRefType(node)] return ownFields[getRefType(node)]
} }
return true return true
} }
@ -51,8 +51,11 @@ func resolveAdditionalProperties(v *jsonschema.Schema) *jsonschema.Schema {
} }
func resolveRefs(s *jsonschema.Schema, schemas map[string]*jsonschema.Schema) *jsonschema.Schema { func resolveRefs(s *jsonschema.Schema, schemas map[string]*jsonschema.Schema) *jsonschema.Schema {
node := s if s == nil {
return nil
}
node := s
description := s.Description description := s.Description
markdownDescription := s.MarkdownDescription markdownDescription := s.MarkdownDescription
examples := s.Examples examples := s.Examples
@ -62,6 +65,7 @@ func resolveRefs(s *jsonschema.Schema, schemas map[string]*jsonschema.Schema) *j
newNode, ok := schemas[ref] newNode, ok := schemas[ref]
if !ok { if !ok {
log.Printf("schema %s not found", ref) log.Printf("schema %s not found", ref)
break
} }
if description == "" { if description == "" {