| 223 | } |
| 224 | |
| 225 | func TestRefineValueSpec(t *testing.T) { |
| 226 | config := ` |
| 227 | foo = "hello" |
| 228 | bar = unk |
| 229 | dyn = dyn |
| 230 | marked = mark(unk) |
| 231 | ` |
| 232 | |
| 233 | f, diags := hclsyntax.ParseConfig([]byte(config), "", hcl.InitialPos) |
| 234 | if diags.HasErrors() { |
| 235 | t.Fatal(diags.Error()) |
| 236 | } |
| 237 | |
| 238 | attrSpec := func(name string) Spec { |
| 239 | return &RefineValueSpec{ |
| 240 | // RefineValueSpec should typically have a ValidateSpec wrapped |
| 241 | // inside it to catch any values that are outside of the required |
| 242 | // range and return a helpful error message about it. In this |
| 243 | // case our refinement is .NotNull so the validation function |
| 244 | // must reject null values. |
| 245 | Wrapped: &ValidateSpec{ |
| 246 | Wrapped: &AttrSpec{ |
| 247 | Name: name, |
| 248 | Required: true, |
| 249 | Type: cty.String, |
| 250 | }, |
| 251 | Func: func(value cty.Value) hcl.Diagnostics { |
| 252 | var diags hcl.Diagnostics |
| 253 | if value.IsNull() { |
| 254 | diags = diags.Append(&hcl.Diagnostic{ |
| 255 | Severity: hcl.DiagError, |
| 256 | Summary: "Cannot be null", |
| 257 | Detail: "Argument is required.", |
| 258 | }) |
| 259 | } |
| 260 | return diags |
| 261 | }, |
| 262 | }, |
| 263 | Refine: func(rb *cty.RefinementBuilder) *cty.RefinementBuilder { |
| 264 | return rb.NotNull() |
| 265 | }, |
| 266 | } |
| 267 | } |
| 268 | spec := &ObjectSpec{ |
| 269 | "foo": attrSpec("foo"), |
| 270 | "bar": attrSpec("bar"), |
| 271 | "dyn": attrSpec("dyn"), |
| 272 | "marked": attrSpec("marked"), |
| 273 | } |
| 274 | |
| 275 | got, diags := Decode(f.Body, spec, &hcl.EvalContext{ |
| 276 | Variables: map[string]cty.Value{ |
| 277 | "unk": cty.UnknownVal(cty.String), |
| 278 | "dyn": cty.DynamicVal, |
| 279 | }, |
| 280 | Functions: map[string]function.Function{ |
| 281 | "mark": function.New(&function.Spec{ |
| 282 | Params: []function.Parameter{ |