1package hclsyntax
2
3import (
4	"bytes"
5	"fmt"
6
7	"github.com/hashicorp/hcl2/hcl"
8	"github.com/zclconf/go-cty/cty"
9	"github.com/zclconf/go-cty/cty/convert"
10)
11
12type TemplateExpr struct {
13	Parts []Expression
14
15	SrcRange hcl.Range
16}
17
18func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
19	for _, part := range e.Parts {
20		w(part)
21	}
22}
23
24func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
25	buf := &bytes.Buffer{}
26	var diags hcl.Diagnostics
27	isKnown := true
28
29	for _, part := range e.Parts {
30		partVal, partDiags := part.Value(ctx)
31		diags = append(diags, partDiags...)
32
33		if partVal.IsNull() {
34			diags = append(diags, &hcl.Diagnostic{
35				Severity: hcl.DiagError,
36				Summary:  "Invalid template interpolation value",
37				Detail: fmt.Sprintf(
38					"The expression result is null. Cannot include a null value in a string template.",
39				),
40				Subject:     part.Range().Ptr(),
41				Context:     &e.SrcRange,
42				Expression:  part,
43				EvalContext: ctx,
44			})
45			continue
46		}
47
48		if !partVal.IsKnown() {
49			// If any part is unknown then the result as a whole must be
50			// unknown too. We'll keep on processing the rest of the parts
51			// anyway, because we want to still emit any diagnostics resulting
52			// from evaluating those.
53			isKnown = false
54			continue
55		}
56
57		strVal, err := convert.Convert(partVal, cty.String)
58		if err != nil {
59			diags = append(diags, &hcl.Diagnostic{
60				Severity: hcl.DiagError,
61				Summary:  "Invalid template interpolation value",
62				Detail: fmt.Sprintf(
63					"Cannot include the given value in a string template: %s.",
64					err.Error(),
65				),
66				Subject:     part.Range().Ptr(),
67				Context:     &e.SrcRange,
68				Expression:  part,
69				EvalContext: ctx,
70			})
71			continue
72		}
73
74		buf.WriteString(strVal.AsString())
75	}
76
77	if !isKnown {
78		return cty.UnknownVal(cty.String), diags
79	}
80
81	return cty.StringVal(buf.String()), diags
82}
83
84func (e *TemplateExpr) Range() hcl.Range {
85	return e.SrcRange
86}
87
88func (e *TemplateExpr) StartRange() hcl.Range {
89	return e.Parts[0].StartRange()
90}
91
92// IsStringLiteral returns true if and only if the template consists only of
93// single string literal, as would be created for a simple quoted string like
94// "foo".
95//
96// If this function returns true, then calling Value on the same expression
97// with a nil EvalContext will return the literal value.
98//
99// Note that "${"foo"}", "${1}", etc aren't considered literal values for the
100// purposes of this method, because the intent of this method is to identify
101// situations where the user seems to be explicitly intending literal string
102// interpretation, not situations that result in literals as a technicality
103// of the template expression unwrapping behavior.
104func (e *TemplateExpr) IsStringLiteral() bool {
105	if len(e.Parts) != 1 {
106		return false
107	}
108	_, ok := e.Parts[0].(*LiteralValueExpr)
109	return ok
110}
111
112// TemplateJoinExpr is used to convert tuples of strings produced by template
113// constructs (i.e. for loops) into flat strings, by converting the values
114// tos strings and joining them. This AST node is not used directly; it's
115// produced as part of the AST of a "for" loop in a template.
116type TemplateJoinExpr struct {
117	Tuple Expression
118}
119
120func (e *TemplateJoinExpr) walkChildNodes(w internalWalkFunc) {
121	w(e.Tuple)
122}
123
124func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
125	tuple, diags := e.Tuple.Value(ctx)
126
127	if tuple.IsNull() {
128		// This indicates a bug in the code that constructed the AST.
129		panic("TemplateJoinExpr got null tuple")
130	}
131	if tuple.Type() == cty.DynamicPseudoType {
132		return cty.UnknownVal(cty.String), diags
133	}
134	if !tuple.Type().IsTupleType() {
135		// This indicates a bug in the code that constructed the AST.
136		panic("TemplateJoinExpr got non-tuple tuple")
137	}
138	if !tuple.IsKnown() {
139		return cty.UnknownVal(cty.String), diags
140	}
141
142	buf := &bytes.Buffer{}
143	it := tuple.ElementIterator()
144	for it.Next() {
145		_, val := it.Element()
146
147		if val.IsNull() {
148			diags = append(diags, &hcl.Diagnostic{
149				Severity: hcl.DiagError,
150				Summary:  "Invalid template interpolation value",
151				Detail: fmt.Sprintf(
152					"An iteration result is null. Cannot include a null value in a string template.",
153				),
154				Subject:     e.Range().Ptr(),
155				Expression:  e,
156				EvalContext: ctx,
157			})
158			continue
159		}
160		if val.Type() == cty.DynamicPseudoType {
161			return cty.UnknownVal(cty.String), diags
162		}
163		strVal, err := convert.Convert(val, cty.String)
164		if err != nil {
165			diags = append(diags, &hcl.Diagnostic{
166				Severity: hcl.DiagError,
167				Summary:  "Invalid template interpolation value",
168				Detail: fmt.Sprintf(
169					"Cannot include one of the interpolation results into the string template: %s.",
170					err.Error(),
171				),
172				Subject:     e.Range().Ptr(),
173				Expression:  e,
174				EvalContext: ctx,
175			})
176			continue
177		}
178		if !val.IsKnown() {
179			return cty.UnknownVal(cty.String), diags
180		}
181
182		buf.WriteString(strVal.AsString())
183	}
184
185	return cty.StringVal(buf.String()), diags
186}
187
188func (e *TemplateJoinExpr) Range() hcl.Range {
189	return e.Tuple.Range()
190}
191
192func (e *TemplateJoinExpr) StartRange() hcl.Range {
193	return e.Tuple.StartRange()
194}
195
196// TemplateWrapExpr is used instead of a TemplateExpr when a template
197// consists _only_ of a single interpolation sequence. In that case, the
198// template's result is the single interpolation's result, verbatim with
199// no type conversions.
200type TemplateWrapExpr struct {
201	Wrapped Expression
202
203	SrcRange hcl.Range
204}
205
206func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) {
207	w(e.Wrapped)
208}
209
210func (e *TemplateWrapExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
211	return e.Wrapped.Value(ctx)
212}
213
214func (e *TemplateWrapExpr) Range() hcl.Range {
215	return e.SrcRange
216}
217
218func (e *TemplateWrapExpr) StartRange() hcl.Range {
219	return e.SrcRange
220}
221