From 1ba92ee170ac6300d0538850241a953118bc85ef Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Sun, 4 Feb 2018 09:59:20 -0800 Subject: cmd/hcldec: "transform" spec type This new spec type allows evaluating an arbitrary expression on the result of a nested spec, for situations where the a value must be transformed in some way. --- cmd/hcldec/spec-format.md | 25 ++++++++++++++++++++++++ cmd/hcldec/spec.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) (limited to 'cmd') diff --git a/cmd/hcldec/spec-format.md b/cmd/hcldec/spec-format.md index 7ba709b..2126d69 100644 --- a/cmd/hcldec/spec-format.md +++ b/cmd/hcldec/spec-format.md @@ -305,6 +305,31 @@ their usual behavior but are not able to impose validation constraints on the current body since they are not evaluated unless all prior specs produce `null` as their result. +## `transform` spec blocks + +The `transform` spec type evaluates one nested spec and then evaluates a given +expression with that nested spec result to produce a final value. +It creates no validation constraints of its own, but passes on the validation +constraints from its nested block. + +```hcl +transform { + attr { + name = "size_in_mb" + type = number + } + + # Convert result to a size in bytes + result = nested * 1024 * 1024 +} +``` + +`transform` spec blocks accept the following argument: + +* `result` (required) - The expression to evaluate on the result of the nested + spec. The variable `nested` is defined when evaluating this expression, with + the result value of the nested spec. + ## Type Expressions Type expressions are used to describe the expected type of an attribute, as diff --git a/cmd/hcldec/spec.go b/cmd/hcldec/spec.go index 6081d68..fa09663 100644 --- a/cmd/hcldec/spec.go +++ b/cmd/hcldec/spec.go @@ -85,6 +85,9 @@ func decodeSpecBlock(block *hcl.Block) (hcldec.Spec, hcl.Diagnostics) { case "default": return decodeDefaultSpec(block.Body) + case "transform": + return decodeTransformSpec(block.Body) + case "literal": return decodeLiteralSpec(block.Body) @@ -439,6 +442,50 @@ func decodeDefaultSpec(body hcl.Body) (hcldec.Spec, hcl.Diagnostics) { return spec, diags } +func decodeTransformSpec(body hcl.Body) (hcldec.Spec, hcl.Diagnostics) { + type content struct { + Result hcl.Expression `hcl:"result"` + Nested hcl.Body `hcl:",remain"` + } + + var args content + diags := gohcl.DecodeBody(body, nil, &args) + if diags.HasErrors() { + return errSpec, diags + } + + spec := &hcldec.TransformExprSpec{ + Expr: args.Result, + VarName: "nested", + } + + nestedContent, nestedDiags := args.Nested.Content(specSchemaUnlabelled) + diags = append(diags, nestedDiags...) + + if len(nestedContent.Blocks) != 1 { + if nestedDiags.HasErrors() { + // If we already have errors then they probably explain + // why we have the wrong number of blocks, so we'll skip our + // additional error message added below. + return errSpec, diags + } + + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid transform spec", + Detail: "A transform spec block must have exactly one nested spec block.", + Subject: body.MissingItemRange().Ptr(), + }) + return errSpec, diags + } + + nestedSpec, nestedDiags := decodeSpecBlock(nestedContent.Blocks[0]) + diags = append(diags, nestedDiags...) + spec.Wrapped = nestedSpec + + return spec, diags +} + var errSpec = &hcldec.LiteralSpec{ Value: cty.NullVal(cty.DynamicPseudoType), } @@ -457,6 +504,7 @@ var specBlockTypes = []string{ "block_set", "default", + "transform", } var specSchemaUnlabelled *hcl.BodySchema -- cgit v1.2.3