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  }