github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/plugins/shared/hclspec/dec.go (about) 1 package hclspec 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl2/hcl" 7 "github.com/hashicorp/hcl2/hcl/hclsyntax" 8 "github.com/hashicorp/hcl2/hcldec" 9 ) 10 11 var ( 12 // nilSpecDiagnostic is the diagnostic value returned if a nil value is 13 // given 14 nilSpecDiagnostic = &hcl.Diagnostic{ 15 Severity: hcl.DiagError, 16 Summary: "nil spec given", 17 Detail: "Can not convert a nil specification. Pass a valid spec", 18 } 19 20 // emptyPos is the position used when parsing hcl expressions 21 emptyPos = hcl.Pos{ 22 Line: 0, 23 Column: 0, 24 Byte: 0, 25 } 26 27 // specCtx is the context used to evaluate expressions. 28 specCtx = &hcl.EvalContext{ 29 Functions: specFuncs, 30 } 31 ) 32 33 // Convert converts a Spec to an hcl specification. 34 func Convert(spec *Spec) (hcldec.Spec, hcl.Diagnostics) { 35 if spec == nil { 36 return nil, hcl.Diagnostics([]*hcl.Diagnostic{nilSpecDiagnostic}) 37 } 38 39 return decodeSpecBlock(spec, "") 40 } 41 42 // decodeSpecBlock is the recursive entry point that converts between the two 43 // spec types. 44 func decodeSpecBlock(spec *Spec, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 45 switch spec.Block.(type) { 46 47 case *Spec_Object: 48 return decodeObjectSpec(spec.GetObject()) 49 50 case *Spec_Array: 51 return decodeArraySpec(spec.GetArray()) 52 53 case *Spec_Attr: 54 return decodeAttrSpec(spec.GetAttr(), impliedName) 55 56 case *Spec_BlockValue: 57 return decodeBlockSpec(spec.GetBlockValue(), impliedName) 58 59 case *Spec_BlockList: 60 return decodeBlockListSpec(spec.GetBlockList(), impliedName) 61 62 case *Spec_BlockSet: 63 return decodeBlockSetSpec(spec.GetBlockSet(), impliedName) 64 65 case *Spec_BlockMap: 66 return decodeBlockMapSpec(spec.GetBlockMap(), impliedName) 67 68 case *Spec_Default: 69 return decodeDefaultSpec(spec.GetDefault()) 70 71 case *Spec_Literal: 72 return decodeLiteralSpec(spec.GetLiteral()) 73 74 default: 75 // Should never happen, because the above cases should be exhaustive 76 // for our schema. 77 var diags hcl.Diagnostics 78 diags = append(diags, &hcl.Diagnostic{ 79 Severity: hcl.DiagError, 80 Summary: "Invalid spec block", 81 Detail: fmt.Sprintf("Blocks of type %T are not expected here.", spec.Block), 82 }) 83 return nil, diags 84 } 85 } 86 87 func decodeObjectSpec(obj *Object) (hcldec.Spec, hcl.Diagnostics) { 88 var diags hcl.Diagnostics 89 spec := make(hcldec.ObjectSpec) 90 for attr, block := range obj.GetAttributes() { 91 propSpec, propDiags := decodeSpecBlock(block, attr) 92 diags = append(diags, propDiags...) 93 spec[attr] = propSpec 94 } 95 96 return spec, diags 97 } 98 99 func decodeArraySpec(a *Array) (hcldec.Spec, hcl.Diagnostics) { 100 values := a.GetValues() 101 var diags hcl.Diagnostics 102 spec := make(hcldec.TupleSpec, 0, len(values)) 103 for _, block := range values { 104 elemSpec, elemDiags := decodeSpecBlock(block, "") 105 diags = append(diags, elemDiags...) 106 spec = append(spec, elemSpec) 107 } 108 109 return spec, diags 110 } 111 112 func decodeAttrSpec(attr *Attr, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 113 // Convert the string type to an hcl.Expression 114 typeExpr, diags := hclsyntax.ParseExpression([]byte(attr.GetType()), "proto", emptyPos) 115 if diags.HasErrors() { 116 return nil, diags 117 } 118 119 spec := &hcldec.AttrSpec{ 120 Name: impliedName, 121 Required: attr.GetRequired(), 122 } 123 124 if n := attr.GetName(); n != "" { 125 spec.Name = n 126 } 127 128 var typeDiags hcl.Diagnostics 129 spec.Type, typeDiags = evalTypeExpr(typeExpr) 130 diags = append(diags, typeDiags...) 131 132 if spec.Name == "" { 133 diags = append(diags, &hcl.Diagnostic{ 134 Severity: hcl.DiagError, 135 Summary: "Missing name in attribute spec", 136 Detail: "The name attribute is required, to specify the attribute name that is expected in an input HCL file.", 137 }) 138 return nil, diags 139 } 140 141 return spec, diags 142 } 143 144 func decodeBlockSpec(block *Block, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 145 spec := &hcldec.BlockSpec{ 146 TypeName: impliedName, 147 Required: block.GetRequired(), 148 } 149 150 if n := block.GetName(); n != "" { 151 spec.TypeName = n 152 } 153 154 nested, diags := decodeBlockNestedSpec(block.GetNested()) 155 spec.Nested = nested 156 return spec, diags 157 } 158 159 func decodeBlockListSpec(block *BlockList, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 160 spec := &hcldec.BlockListSpec{ 161 TypeName: impliedName, 162 MinItems: int(block.GetMinItems()), 163 MaxItems: int(block.GetMaxItems()), 164 } 165 166 if n := block.GetName(); n != "" { 167 spec.TypeName = n 168 } 169 170 nested, diags := decodeBlockNestedSpec(block.GetNested()) 171 spec.Nested = nested 172 173 if spec.TypeName == "" { 174 diags = append(diags, &hcl.Diagnostic{ 175 Severity: hcl.DiagError, 176 Summary: "Missing name in block_list spec", 177 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 178 }) 179 return nil, diags 180 } 181 182 return spec, diags 183 } 184 185 func decodeBlockSetSpec(block *BlockSet, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 186 spec := &hcldec.BlockSetSpec{ 187 TypeName: impliedName, 188 MinItems: int(block.GetMinItems()), 189 MaxItems: int(block.GetMaxItems()), 190 } 191 192 if n := block.GetName(); n != "" { 193 spec.TypeName = n 194 } 195 196 nested, diags := decodeBlockNestedSpec(block.GetNested()) 197 spec.Nested = nested 198 199 if spec.TypeName == "" { 200 diags = append(diags, &hcl.Diagnostic{ 201 Severity: hcl.DiagError, 202 Summary: "Missing name in block_set spec", 203 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 204 }) 205 return nil, diags 206 } 207 208 return spec, diags 209 } 210 211 func decodeBlockMapSpec(block *BlockMap, impliedName string) (hcldec.Spec, hcl.Diagnostics) { 212 spec := &hcldec.BlockMapSpec{ 213 TypeName: impliedName, 214 LabelNames: block.GetLabels(), 215 } 216 217 if n := block.GetName(); n != "" { 218 spec.TypeName = n 219 } 220 221 nested, diags := decodeBlockNestedSpec(block.GetNested()) 222 spec.Nested = nested 223 224 if spec.TypeName == "" { 225 diags = append(diags, &hcl.Diagnostic{ 226 Severity: hcl.DiagError, 227 Summary: "Missing name in block_map spec", 228 Detail: "The name attribute is required, to specify the block type name that is expected in an input HCL file.", 229 }) 230 return nil, diags 231 } 232 if len(spec.LabelNames) < 1 { 233 diags = append(diags, &hcl.Diagnostic{ 234 Severity: hcl.DiagError, 235 Summary: "Invalid block label name list", 236 Detail: "A block_map must have at least one label specified.", 237 }) 238 return nil, diags 239 } 240 241 return spec, diags 242 } 243 244 func decodeBlockNestedSpec(spec *Spec) (hcldec.Spec, hcl.Diagnostics) { 245 if spec == nil { 246 return nil, hcl.Diagnostics([]*hcl.Diagnostic{ 247 { 248 Severity: hcl.DiagError, 249 Summary: "Missing spec block", 250 Detail: "A block spec must have exactly one child spec specifying how to decode block contents.", 251 }}) 252 } 253 254 return decodeSpecBlock(spec, "") 255 } 256 257 func decodeLiteralSpec(l *Literal) (hcldec.Spec, hcl.Diagnostics) { 258 // Convert the string value to an hcl.Expression 259 valueExpr, diags := hclsyntax.ParseExpression([]byte(l.GetValue()), "proto", emptyPos) 260 if diags.HasErrors() { 261 return nil, diags 262 } 263 264 value, valueDiags := valueExpr.Value(specCtx) 265 diags = append(diags, valueDiags...) 266 if diags.HasErrors() { 267 return nil, diags 268 } 269 270 return &hcldec.LiteralSpec{ 271 Value: value, 272 }, diags 273 } 274 275 func decodeDefaultSpec(d *Default) (hcldec.Spec, hcl.Diagnostics) { 276 // Parse the primary 277 primary, diags := decodeSpecBlock(d.GetPrimary(), "") 278 if diags.HasErrors() { 279 return nil, diags 280 } 281 282 // Parse the default 283 def, defDiags := decodeSpecBlock(d.GetDefault(), "") 284 diags = append(diags, defDiags...) 285 if diags.HasErrors() { 286 return nil, diags 287 } 288 289 spec := &hcldec.DefaultSpec{ 290 Primary: primary, 291 Default: def, 292 } 293 294 return spec, diags 295 }