github.com/hashicorp/hcl/v2@v2.20.0/hcltest/mock.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcltest 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2/hclsyntax" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 // MockBody returns a hcl.Body implementation that works in terms of a 16 // caller-constructed hcl.BodyContent, thus avoiding the need to parse 17 // a "real" HCL config file to use as input to a test. 18 func MockBody(content *hcl.BodyContent) hcl.Body { 19 return mockBody{content} 20 } 21 22 type mockBody struct { 23 C *hcl.BodyContent 24 } 25 26 func (b mockBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 27 content, remainI, diags := b.PartialContent(schema) 28 remain := remainI.(mockBody) 29 for _, attr := range remain.C.Attributes { 30 diags = append(diags, &hcl.Diagnostic{ 31 Severity: hcl.DiagError, 32 Summary: "Extraneous argument in mock body", 33 Detail: fmt.Sprintf("Mock body has extraneous argument %q.", attr.Name), 34 Subject: &attr.NameRange, 35 }) 36 } 37 for _, block := range remain.C.Blocks { 38 diags = append(diags, &hcl.Diagnostic{ 39 Severity: hcl.DiagError, 40 Summary: "Extraneous block in mock body", 41 Detail: fmt.Sprintf("Mock body has extraneous block of type %q.", block.Type), 42 Subject: &block.DefRange, 43 }) 44 } 45 return content, diags 46 } 47 48 func (b mockBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 49 ret := &hcl.BodyContent{ 50 Attributes: map[string]*hcl.Attribute{}, 51 Blocks: []*hcl.Block{}, 52 MissingItemRange: b.C.MissingItemRange, 53 } 54 remain := &hcl.BodyContent{ 55 Attributes: map[string]*hcl.Attribute{}, 56 Blocks: []*hcl.Block{}, 57 MissingItemRange: b.C.MissingItemRange, 58 } 59 var diags hcl.Diagnostics 60 61 if len(schema.Attributes) != 0 { 62 for _, attrS := range schema.Attributes { 63 name := attrS.Name 64 attr, ok := b.C.Attributes[name] 65 if !ok { 66 if attrS.Required { 67 diags = append(diags, &hcl.Diagnostic{ 68 Severity: hcl.DiagError, 69 Summary: "Missing required argument", 70 Detail: fmt.Sprintf("Mock body doesn't have argument %q", name), 71 Subject: b.C.MissingItemRange.Ptr(), 72 }) 73 } 74 continue 75 } 76 ret.Attributes[name] = attr 77 } 78 } 79 80 for attrN, attr := range b.C.Attributes { 81 if _, ok := ret.Attributes[attrN]; !ok { 82 remain.Attributes[attrN] = attr 83 } 84 } 85 86 wantedBlocks := map[string]hcl.BlockHeaderSchema{} 87 for _, blockS := range schema.Blocks { 88 wantedBlocks[blockS.Type] = blockS 89 } 90 91 for _, block := range b.C.Blocks { 92 if blockS, ok := wantedBlocks[block.Type]; ok { 93 if len(block.Labels) != len(blockS.LabelNames) { 94 diags = append(diags, &hcl.Diagnostic{ 95 Severity: hcl.DiagError, 96 Summary: "Wrong number of block labels", 97 Detail: fmt.Sprintf("Block of type %q requires %d labels, but got %d", blockS.Type, len(blockS.LabelNames), len(block.Labels)), 98 Subject: b.C.MissingItemRange.Ptr(), 99 }) 100 } 101 102 ret.Blocks = append(ret.Blocks, block) 103 } else { 104 remain.Blocks = append(remain.Blocks, block) 105 } 106 } 107 108 return ret, mockBody{remain}, diags 109 } 110 111 func (b mockBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 112 var diags hcl.Diagnostics 113 if len(b.C.Blocks) != 0 { 114 diags = append(diags, &hcl.Diagnostic{ 115 Severity: hcl.DiagError, 116 Summary: "Mock body has blocks", 117 Detail: "Can't use JustAttributes on a mock body with blocks.", 118 Subject: b.C.MissingItemRange.Ptr(), 119 }) 120 } 121 122 return b.C.Attributes, diags 123 } 124 125 func (b mockBody) MissingItemRange() hcl.Range { 126 return b.C.MissingItemRange 127 } 128 129 // MockExprLiteral returns a hcl.Expression that evaluates to the given literal 130 // value. 131 func MockExprLiteral(val cty.Value) hcl.Expression { 132 return mockExprLiteral{val} 133 } 134 135 type mockExprLiteral struct { 136 V cty.Value 137 } 138 139 func (e mockExprLiteral) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 140 return e.V, nil 141 } 142 143 func (e mockExprLiteral) Variables() []hcl.Traversal { 144 return nil 145 } 146 147 func (e mockExprLiteral) Range() hcl.Range { 148 return hcl.Range{ 149 Filename: "MockExprLiteral", 150 } 151 } 152 153 func (e mockExprLiteral) StartRange() hcl.Range { 154 return e.Range() 155 } 156 157 // Implementation for hcl.ExprList 158 func (e mockExprLiteral) ExprList() []hcl.Expression { 159 v := e.V 160 ty := v.Type() 161 if v.IsKnown() && !v.IsNull() && (ty.IsListType() || ty.IsTupleType()) { 162 ret := make([]hcl.Expression, 0, v.LengthInt()) 163 for it := v.ElementIterator(); it.Next(); { 164 _, v := it.Element() 165 ret = append(ret, MockExprLiteral(v)) 166 } 167 return ret 168 } 169 return nil 170 } 171 172 // Implementation for hcl.ExprMap 173 func (e mockExprLiteral) ExprMap() []hcl.KeyValuePair { 174 v := e.V 175 ty := v.Type() 176 if v.IsKnown() && !v.IsNull() && (ty.IsObjectType() || ty.IsMapType()) { 177 ret := make([]hcl.KeyValuePair, 0, v.LengthInt()) 178 for it := v.ElementIterator(); it.Next(); { 179 k, v := it.Element() 180 ret = append(ret, hcl.KeyValuePair{ 181 Key: MockExprLiteral(k), 182 Value: MockExprLiteral(v), 183 }) 184 } 185 return ret 186 } 187 return nil 188 } 189 190 // MockExprVariable returns a hcl.Expression that evaluates to the value of 191 // the variable with the given name. 192 func MockExprVariable(name string) hcl.Expression { 193 return mockExprVariable(name) 194 } 195 196 type mockExprVariable string 197 198 func (e mockExprVariable) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 199 name := string(e) 200 for ctx != nil { 201 if val, ok := ctx.Variables[name]; ok { 202 return val, nil 203 } 204 ctx = ctx.Parent() 205 } 206 207 // If we fall out here then there is no variable with the given name 208 return cty.DynamicVal, hcl.Diagnostics{ 209 { 210 Severity: hcl.DiagError, 211 Summary: "Reference to undefined variable", 212 Detail: fmt.Sprintf("Variable %q is not defined.", name), 213 }, 214 } 215 } 216 217 func (e mockExprVariable) Variables() []hcl.Traversal { 218 return []hcl.Traversal{ 219 { 220 hcl.TraverseRoot{ 221 Name: string(e), 222 SrcRange: e.Range(), 223 }, 224 }, 225 } 226 } 227 228 func (e mockExprVariable) Range() hcl.Range { 229 return hcl.Range{ 230 Filename: "MockExprVariable", 231 } 232 } 233 234 func (e mockExprVariable) StartRange() hcl.Range { 235 return e.Range() 236 } 237 238 // Implementation for hcl.AbsTraversalForExpr and hcl.RelTraversalForExpr. 239 func (e mockExprVariable) AsTraversal() hcl.Traversal { 240 return hcl.Traversal{ 241 hcl.TraverseRoot{ 242 Name: string(e), 243 SrcRange: e.Range(), 244 }, 245 } 246 } 247 248 // MockExprTraversal returns a hcl.Expression that evaluates the given 249 // absolute traversal. 250 func MockExprTraversal(traversal hcl.Traversal) hcl.Expression { 251 return mockExprTraversal{ 252 Traversal: traversal, 253 } 254 } 255 256 // MockExprTraversalSrc is like MockExprTraversal except it takes a 257 // traversal string as defined by the native syntax and parses it first. 258 // 259 // This method is primarily for testing with hard-coded traversal strings, so 260 // it will panic if the given string is not syntactically correct. 261 func MockExprTraversalSrc(src string) hcl.Expression { 262 traversal, diags := hclsyntax.ParseTraversalAbs([]byte(src), "MockExprTraversal", hcl.Pos{}) 263 if diags.HasErrors() { 264 panic("invalid traversal string") 265 } 266 return MockExprTraversal(traversal) 267 } 268 269 type mockExprTraversal struct { 270 Traversal hcl.Traversal 271 } 272 273 func (e mockExprTraversal) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 274 return e.Traversal.TraverseAbs(ctx) 275 } 276 277 func (e mockExprTraversal) Variables() []hcl.Traversal { 278 return []hcl.Traversal{e.Traversal} 279 } 280 281 func (e mockExprTraversal) Range() hcl.Range { 282 return e.Traversal.SourceRange() 283 } 284 285 func (e mockExprTraversal) StartRange() hcl.Range { 286 return e.Range() 287 } 288 289 // Implementation for hcl.AbsTraversalForExpr and hcl.RelTraversalForExpr. 290 func (e mockExprTraversal) AsTraversal() hcl.Traversal { 291 return e.Traversal 292 } 293 294 func MockExprList(exprs []hcl.Expression) hcl.Expression { 295 return mockExprList{ 296 Exprs: exprs, 297 } 298 } 299 300 type mockExprList struct { 301 Exprs []hcl.Expression 302 } 303 304 func (e mockExprList) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 305 if len(e.Exprs) == 0 { 306 return cty.ListValEmpty(cty.DynamicPseudoType), nil 307 } 308 vals := make([]cty.Value, 0, len(e.Exprs)) 309 var diags hcl.Diagnostics 310 311 for _, expr := range e.Exprs { 312 val, valDiags := expr.Value(ctx) 313 diags = append(diags, valDiags...) 314 vals = append(vals, val) 315 } 316 317 return cty.ListVal(vals), diags 318 } 319 320 func (e mockExprList) Variables() []hcl.Traversal { 321 var traversals []hcl.Traversal 322 for _, expr := range e.Exprs { 323 traversals = append(traversals, expr.Variables()...) 324 } 325 return traversals 326 } 327 328 func (e mockExprList) Range() hcl.Range { 329 return hcl.Range{ 330 Filename: "MockExprList", 331 } 332 } 333 334 func (e mockExprList) StartRange() hcl.Range { 335 return e.Range() 336 } 337 338 // Implementation for hcl.ExprList 339 func (e mockExprList) ExprList() []hcl.Expression { 340 return e.Exprs 341 } 342 343 // MockAttrs constructs and returns a hcl.Attributes map with attributes 344 // derived from the given expression map. 345 // 346 // Each entry in the map becomes an attribute whose name is the key and 347 // whose expression is the value. 348 func MockAttrs(exprs map[string]hcl.Expression) hcl.Attributes { 349 ret := make(hcl.Attributes) 350 for name, expr := range exprs { 351 ret[name] = &hcl.Attribute{ 352 Name: name, 353 Expr: expr, 354 Range: hcl.Range{ 355 Filename: "MockAttrs", 356 }, 357 NameRange: hcl.Range{ 358 Filename: "MockAttrs", 359 }, 360 } 361 } 362 return ret 363 }