github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/internal/parsers/validationparser/validationparser.go (about) 1 package validationparser 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclparse" 11 "github.com/mineiros-io/terradoc/internal/entities" 12 "github.com/mineiros-io/terradoc/internal/parsers/hclparser" 13 "github.com/mineiros-io/terradoc/internal/schemas/validationschema" 14 "github.com/mineiros-io/terradoc/internal/schemas/varsschema" 15 ) 16 17 func Parse(r io.Reader, filename string, variablesEnabled bool, outputsEnabled bool) (entities.ValidationContents, error) { 18 src, err := io.ReadAll(r) 19 if err != nil { 20 return entities.ValidationContents{}, err 21 } 22 23 return parseContentHCL(src, filename, variablesEnabled, outputsEnabled) 24 } 25 26 func parseContentHCL(src []byte, filename string, variablesEnabled bool, outputsEnabled bool) (entities.ValidationContents, error) { 27 p := hclparse.NewParser() 28 validationContents := entities.ValidationContents{} 29 30 f, diags := p.ParseHCL(src, filename) 31 if diags.HasErrors() { 32 // Only return errors relevant to parsing of variables or outputs 33 var errors []error 34 for _, e := range diags.Errs() { 35 if variablesEnabled && strings.Contains(e.Error(), "variable") { 36 errors = append(errors, e) 37 } 38 39 if outputsEnabled && strings.Contains(e.Error(), "output") { 40 errors = append(errors, e) 41 } 42 } 43 44 if len(errors) > 0 { 45 return entities.ValidationContents{}, fmt.Errorf("parsing HCL: %v", errors) 46 } 47 48 } 49 50 // Ignore errors, only focus on content to ignore non outputs/variables 51 content, _ := f.Body.Content(validationschema.RootSchema()) 52 53 if variablesEnabled { 54 variables, err := parseVariables(content.Blocks.OfType("variable")) 55 if err != nil { 56 return entities.ValidationContents{}, fmt.Errorf("parsing variables: %v", err) 57 } 58 validationContents.Variables = variables 59 } 60 61 if outputsEnabled { 62 outputs, err := parseOutputs(content.Blocks.OfType("output")) 63 if err != nil { 64 return entities.ValidationContents{}, fmt.Errorf("parsing outputs: %v", err) 65 } 66 validationContents.Outputs = outputs 67 } 68 69 return validationContents, nil 70 } 71 72 func parseVariables(variableBlocks hcl.Blocks) (variables []entities.Variable, err error) { 73 for _, varBlk := range variableBlocks { 74 variable, err := parseVariable(varBlk) 75 if err != nil { 76 return nil, fmt.Errorf("parsing variable: %s", err) 77 } 78 79 variables = append(variables, variable) 80 } 81 82 return variables, nil 83 } 84 85 func parseVariable(variableBlock *hcl.Block) (entities.Variable, error) { 86 if len(variableBlock.Labels) != 1 { 87 return entities.Variable{}, errors.New("variable block must have a single label") 88 } 89 90 variableContent, diags := variableBlock.Body.Content(varsschema.VariableSchema()) 91 if diags.HasErrors() && len(variableContent.Attributes) != 1 { 92 var errors []error 93 for _, e := range diags.Errs() { 94 // Only return if parsing error is relevant to `type` 95 if strings.Contains(e.Error(), "type") { 96 errors = append(errors, e) 97 } 98 } 99 100 if len(errors) > 0 { 101 return entities.Variable{}, fmt.Errorf("parsing variable: %v", errors) 102 } 103 } 104 105 // variable blocks are required to have a label as defined in the schema 106 name := variableBlock.Labels[0] 107 variable, err := createVariableFromHCLAttributes(variableContent.Attributes, name) 108 if err != nil { 109 return entities.Variable{}, fmt.Errorf("parsing variable: %s", err) 110 } 111 112 return variable, nil 113 } 114 115 func createVariableFromHCLAttributes(attrs hcl.Attributes, name string) (entities.Variable, error) { 116 var err error 117 118 variable := entities.Variable{Name: name} 119 120 variable.Default, err = hclparser.GetAttribute(attrs, "default").RawJSON() 121 if err != nil { 122 return entities.Variable{}, err 123 } 124 125 // type definition 126 variable.Type, err = hclparser.GetAttribute(attrs, "type").VarType() 127 if err != nil { 128 return entities.Variable{}, err 129 } 130 131 return variable, nil 132 } 133 134 func parseOutputs(outputBlocks hcl.Blocks) (outputs []entities.Output, err error) { 135 for _, outBlk := range outputBlocks { 136 output, err := parseOutput(outBlk) 137 if err != nil { 138 return nil, fmt.Errorf("parsing output: %s", err) 139 } 140 141 outputs = append(outputs, output) 142 } 143 144 return outputs, nil 145 } 146 147 func parseOutput(outputBlock *hcl.Block) (entities.Output, error) { 148 if len(outputBlock.Labels) != 1 { 149 return entities.Output{}, errors.New("output block must have a single label") 150 } 151 152 // output blocks are required to have a label as defined in the schema 153 name := outputBlock.Labels[0] 154 output := entities.Output{Name: name} 155 156 return output, nil 157 }