provider-schema/processors/combine.go (113 lines of code) (raw):
package processors
import (
"fmt"
"strings"
"github.com/Azure/azurerm-lsp/provider-schema/azurerm/schema"
"github.com/Azure/azurerm-lsp/provider-schema/processors/.tools/document-lint/model"
)
// CombineSchemaAndMarkdown merges markdown fields into the schema and returns the combined resources.
func CombineSchemaAndMarkdown(providerSchema *schema.ProviderSchema, markdownDocs map[string]*model.ResourceDoc) (TerraformObjects, error) {
terraformObjects := make(TerraformObjects)
// Combine resources and data sources
for name, resourceSchema := range providerSchema.ResourceSchemas {
terraformObject := &TerraformObject{
Name: name,
Fields: make(map[string]*schema.SchemaAttribute),
}
terraformObjects[name] = terraformObject
markdownDoc, exists := markdownDocs[name]
if !exists || markdownDoc == nil {
//fmt.Println("Resource/DataSource not found in documentation:", name)
continue
}
terraformObject.ExampleHCL = markdownDoc.ExampleHCL
terraformObject.Timeouts = markdownDoc.Timeouts
terraformObject.Import = markdownDoc.Import
terraformObject.Details = extractDetails(markdownDoc.Content)
// Inject descriptions from markdown into the providerSchema fields
combineFieldsRecursively(name, resourceSchema.Block, markdownDoc.AllProp(), terraformObject.Fields, "")
}
return terraformObjects, nil
}
func extractDetails(content string) string {
lines := strings.Split(content, "\n")
for i, line := range lines {
if strings.HasPrefix(line, "#") {
lines = lines[i:]
break
}
}
if len(lines) > 0 {
// remove the first line (the resource name)
lines = lines[1:]
}
if len(lines) > 0 {
// remove empty lines at the beginning
curFirstLine := strings.TrimSpace(lines[0])
if curFirstLine == "" || curFirstLine == "\n" {
lines = lines[1:]
}
}
return strings.Join(lines, "\n")
}
// combineFieldsRecursively recursively combines fields from the schema and markdown properties.
func combineFieldsRecursively(resourceName string, schemaBlock *schema.SchemaBlock, markdownProps model.Properties, fields map[string]*schema.SchemaAttribute, attributePath string) {
// Inject descriptions for attributes (fields)
for fieldName, schemaField := range schemaBlock.Attributes {
schemaField.Name = fieldName
schemaField.ResourceOrDataSourceName = resourceName
schemaField.AttributePath = buildAttributePath(attributePath, fieldName)
if markdownProps != nil {
markdownField := markdownProps[fieldName]
if markdownField != nil {
schemaField.Content = getDescription(markdownField.Content)
schemaField.PossibleValues = markdownField.PossibleValues()
} else {
fmt.Printf("(TerraformObject %s) Field not found in documentation: %s\n", resourceName, schemaField.AttributePath)
}
}
fields[schemaField.Name] = schemaField
}
// Inject descriptions for nested blocks
for blockName, schemaBlockType := range schemaBlock.NestedBlocks {
combinedBlockField := &schema.SchemaAttribute{
Name: blockName,
AttributeType: schemaBlockType.Block.ImpliedType(),
Required: schemaBlockType.Required,
Optional: schemaBlockType.Optional,
Computed: schemaBlockType.Computed,
ConflictsWith: schemaBlockType.ConflictsWith,
ExactlyOneOf: schemaBlockType.ExactlyOneOf,
AtLeastOneOf: schemaBlockType.AtLeastOneOf,
RequiredWith: schemaBlockType.RequiredWith,
ResourceOrDataSourceName: resourceName,
AttributePath: buildAttributePath(attributePath, blockName),
NestingMode: schemaBlockType.NestingMode,
Fields: make(map[string]*schema.SchemaAttribute),
}
// Inject description from markdown if available
if markdownProps != nil {
markdownBlockProps := markdownProps.FindAllSubBlock(blockName)
if len(markdownBlockProps) > 0 {
// Use the first field's description as the block description
combinedBlockField.Content = getDescription(markdownBlockProps[0].Content)
combinedBlockField.PossibleValues = markdownBlockProps[0].PossibleValues()
// Inject descriptions for nested attributes
combineFieldsRecursively(resourceName, schemaBlockType.Block, markdownBlockProps[0].Subs, combinedBlockField.Fields, combinedBlockField.AttributePath)
} else {
fmt.Printf("(TerraformObject %s) Block not found in documentation: %s\n", resourceName, combinedBlockField.AttributePath)
}
}
fields[combinedBlockField.Name] = combinedBlockField
}
}
func getDescription(content string) string {
parts := strings.SplitN(content, "-", 2)
if len(parts) < 2 {
return content
}
description := strings.TrimSpace(parts[1])
possibleTypesPrefix := []string{"Optional", "Required", "(Optional)", "(Required)"}
for _, prefix := range possibleTypesPrefix {
if strings.HasPrefix(description, prefix) {
description = strings.TrimPrefix(description, prefix)
description = strings.TrimSpace(description)
break
}
}
return description
}
// buildAttributePath constructs the attribute path for a field in format "parent.field".
func buildAttributePath(parentPath string, fieldName string) string {
if parentPath == "" {
return fieldName
}
return fmt.Sprintf("%s.%s", parentPath, fieldName)
}