databricks-cli/bundle/internal/docs/nodes.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

193 lines
5.2 KiB
Go
Raw Normal View History

package main
import (
"sort"
"strings"
"github.com/databricks/cli/libs/jsonschema"
)
// rootNode is an intermediate representation of resolved JSON-schema item that is used to generate documentation
// Every schema node goes follows this conversion `JSON-schema -> rootNode -> markdown text`
type rootNode struct {
Title string
Description string
Attributes []attributeNode
Example string
ObjectKeyAttributes []attributeNode
ArrayItemAttributes []attributeNode
TopLevel bool
2025-01-03 12:52:59 +00:00
Type string
}
type attributeNode struct {
Title string
Type string
Description string
2025-01-10 11:52:20 +00:00
Reference string
}
type rootProp struct {
// k is the name of the property
k string
// v is the corresponding json-schema node
v *jsonschema.Schema
// topLevel is true only for direct properties of the schema of root type (e.g. config.Root or config.Resources)
// Example: config.Root has .
topLevel bool
// circular indicates if property was added by recursive type, e.g. task.for_each_task.task.for_each_task
// These entries don't expand further and don't add any new nodes from their properties
2025-01-13 14:55:49 +00:00
circular bool
}
2025-01-10 11:52:20 +00:00
const MapType = "Map"
2024-12-18 19:53:38 +00:00
func getNodes(s jsonschema.Schema, refs map[string]jsonschema.Schema, customFields map[string]bool) []rootNode {
rootProps := []rootProp{}
for k, v := range s.Properties {
2025-01-13 14:55:49 +00:00
rootProps = append(rootProps, rootProp{k, v, true, false})
}
nodes := make([]rootNode, 0, len(rootProps))
2025-01-10 11:52:20 +00:00
visited := make(map[string]bool)
for i := 0; i < len(rootProps); i++ {
2024-12-18 19:53:38 +00:00
item := rootProps[i]
k := item.k
v := item.v
2025-01-10 11:52:20 +00:00
if visited[k] {
continue
}
visited[k] = true
v = resolveRefs(v, refs)
node := rootNode{
Title: k,
2024-12-18 19:53:38 +00:00
Description: getDescription(v, item.topLevel),
TopLevel: item.topLevel,
2025-01-02 15:13:24 +00:00
Example: getExample(v),
2025-01-03 12:52:59 +00:00
Type: getHumanReadableType(v.Type),
}
2025-01-13 14:55:49 +00:00
node.Attributes = getAttributes(v.Properties, refs, customFields, k, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(k, v.Properties, refs, customFields)...)
}
additionalProps, ok := v.AdditionalProperties.(*jsonschema.Schema)
if ok {
objectKeyType := resolveRefs(additionalProps, refs)
2025-01-10 11:52:20 +00:00
d := getDescription(objectKeyType, true)
if d != "" {
node.Description = d
}
if len(node.Example) == 0 {
node.Example = getExample(objectKeyType)
}
2025-01-13 14:16:47 +00:00
prefix := k + ".<name>"
2025-01-13 14:55:49 +00:00
node.ObjectKeyAttributes = getAttributes(objectKeyType.Properties, refs, customFields, prefix, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(prefix, objectKeyType.Properties, refs, customFields)...)
}
}
if v.Items != nil {
arrayItemType := resolveRefs(v.Items, refs)
2025-01-13 14:55:49 +00:00
node.ArrayItemAttributes = getAttributes(arrayItemType.Properties, refs, customFields, k, item.circular)
if !item.circular {
rootProps = append(rootProps, extractNodes(k, arrayItemType.Properties, refs, customFields)...)
}
}
2025-01-13 14:28:41 +00:00
nodes = append(nodes, node)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Title < nodes[j].Title
})
return nodes
}
2025-01-03 12:52:59 +00:00
func removePluralForm(s string) string {
if strings.HasSuffix(s, "s") {
return strings.TrimSuffix(s, "s")
}
return s
}
func getHumanReadableType(t jsonschema.Type) string {
typesMapping := map[string]string{
"string": "String",
"integer": "Integer",
"boolean": "Boolean",
"array": "Sequence",
"object": "Map",
}
2025-01-03 12:52:59 +00:00
return typesMapping[string(t)]
}
2025-01-13 14:55:49 +00:00
func getAttributes(props map[string]*jsonschema.Schema, refs map[string]jsonschema.Schema, customFields map[string]bool, prefix string, circular bool) []attributeNode {
attributes := []attributeNode{}
for k, v := range props {
v = resolveRefs(v, refs)
2025-01-03 12:52:59 +00:00
typeString := getHumanReadableType(v.Type)
if typeString == "" {
typeString = "Any"
}
2025-01-10 11:52:20 +00:00
var reference string
2025-01-13 14:55:49 +00:00
if isReferenceType(v, refs, customFields) && !circular {
2025-01-10 11:52:20 +00:00
reference = prefix + "." + k
}
attributes = append(attributes, attributeNode{
Title: k,
Type: typeString,
2024-12-18 19:53:38 +00:00
Description: getDescription(v, true),
2025-01-10 11:52:20 +00:00
Reference: reference,
})
}
sort.Slice(attributes, func(i, j int) bool {
return attributes[i].Title < attributes[j].Title
})
return attributes
}
2024-12-18 19:53:38 +00:00
func getDescription(s *jsonschema.Schema, allowMarkdown bool) string {
if allowMarkdown && s.MarkdownDescription != "" {
return s.MarkdownDescription
}
return s.Description
}
2024-12-18 19:53:38 +00:00
func shouldExtract(ref string, customFields map[string]bool) bool {
2025-01-10 11:52:20 +00:00
if i := strings.Index(ref, "github.com"); i >= 0 {
ref = ref[i:]
}
_, isCustomField := customFields[ref]
2024-12-18 19:53:38 +00:00
return isCustomField
}
func extractNodes(prefix string, props map[string]*jsonschema.Schema, refs map[string]jsonschema.Schema, customFields map[string]bool) []rootProp {
nodes := []rootProp{}
for k, v := range props {
2024-12-18 19:53:38 +00:00
if !shouldExtract(*v.Reference, customFields) {
continue
}
v = resolveRefs(v, refs)
2025-01-10 11:52:20 +00:00
if v.Type == "object" || v.Type == "array" {
2025-01-13 14:55:49 +00:00
nodes = append(nodes, rootProp{prefix + "." + k, v, false, isCycleField(k)})
}
}
return nodes
}
2025-01-02 15:13:24 +00:00
2025-01-13 14:55:49 +00:00
func isCycleField(field string) bool {
return field == "for_each_task"
}
2025-01-02 15:13:24 +00:00
func getExample(v *jsonschema.Schema) string {
examples := v.Examples
if len(examples) == 0 {
return ""
}
return examples[0].(string)
}