| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package jsonfunction | |
| import ( | |
| "fmt" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/hashicorp/hcl/v2/ext/tryfunc" | |
| "github.com/opentofu/opentofu/internal/lang" | |
| "github.com/zclconf/go-cty-debug/ctydebug" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/zclconf/go-cty/cty/function" | |
| ) | |
| func TestMarshal(t *testing.T) { | |
| // We'll use a temporary scope just to get access to a few pesky functions | |
| // that have special handling that we want to test. | |
| tempScope := &lang.Scope{} | |
| funcs := tempScope.Functions() | |
| tests := []struct { | |
| Name string | |
| Input map[string]function.Function | |
| Want string | |
| WantErr string | |
| }{ | |
| { | |
| "minimal function", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| Type: function.StaticReturnType(cty.Bool), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"fun":{"return_type":"bool"}}}`, | |
| "", | |
| }, | |
| { | |
| "function with description", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| Description: "`timestamp` returns a UTC timestamp string.", | |
| Type: function.StaticReturnType(cty.String), | |
| }), | |
| }, | |
| "{\"format_version\":\"1.0\",\"function_signatures\":{\"fun\":{\"description\":\"`timestamp` returns a UTC timestamp string.\",\"return_type\":\"string\"}}}", | |
| "", | |
| }, | |
| { | |
| "function with parameters", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| Params: []function.Parameter{ | |
| { | |
| Name: "timestamp", | |
| Description: "timestamp text", | |
| Type: cty.String, | |
| }, | |
| { | |
| Name: "duration", | |
| Description: "duration text", | |
| Type: cty.String, | |
| }, | |
| }, | |
| Type: function.StaticReturnType(cty.String), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"fun":{"return_type":"string","parameters":[{"name":"timestamp","description":"timestamp text","type":"string"},{"name":"duration","description":"duration text","type":"string"}]}}}`, | |
| "", | |
| }, | |
| { | |
| "function with variadic parameter", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| VarParam: &function.Parameter{ | |
| Name: "default", | |
| Description: "default description", | |
| Type: cty.DynamicPseudoType, | |
| AllowUnknown: true, | |
| AllowDynamicType: true, | |
| AllowNull: true, | |
| AllowMarked: true, | |
| }, | |
| Type: function.StaticReturnType(cty.DynamicPseudoType), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"fun":{"return_type":"dynamic","variadic_parameter":{"name":"default","description":"default description","is_nullable":true,"type":"dynamic"}}}}`, | |
| "", | |
| }, | |
| { | |
| "function with list types", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| Params: []function.Parameter{ | |
| { | |
| Name: "list", | |
| Type: cty.List(cty.String), | |
| }, | |
| }, | |
| Type: function.StaticReturnType(cty.List(cty.String)), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"fun":{"return_type":["list","string"],"parameters":[{"name":"list","type":["list","string"]}]}}}`, | |
| "", | |
| }, | |
| { | |
| "returns diagnostics on failure", | |
| map[string]function.Function{ | |
| "fun": function.New(&function.Spec{ | |
| Params: []function.Parameter{}, | |
| Type: func(args []cty.Value) (ret cty.Type, err error) { | |
| return cty.DynamicPseudoType, fmt.Errorf("error") | |
| }, | |
| }), | |
| }, | |
| "", | |
| "Failed to serialize function \"fun\": error", | |
| }, | |
| { | |
| "try function marshalled correctly", | |
| map[string]function.Function{ | |
| "try": tryfunc.TryFunc, | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"try":{"return_type":"dynamic","variadic_parameter":{"name":"expressions","type":"dynamic"}}}}`, | |
| "", | |
| }, | |
| { | |
| "core::try function marshalled correctly", | |
| map[string]function.Function{ | |
| "core::try": tryfunc.TryFunc, | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"core::try":{"return_type":"dynamic","variadic_parameter":{"name":"expressions","type":"dynamic"}}}}`, | |
| "", | |
| }, | |
| { | |
| // This checks that if a provider contains a function named the same as one of the core with custom marshaller, we identify that correctly | |
| "provider::test::try function marshalled correctly", | |
| map[string]function.Function{ | |
| "provider::test::try": function.New(&function.Spec{ | |
| Params: []function.Parameter{ | |
| { | |
| Name: "list", | |
| Type: cty.List(cty.String), | |
| }, | |
| }, | |
| Type: function.StaticReturnType(cty.List(cty.String)), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"provider::test::try":{"return_type":["list","string"],"parameters":[{"name":"list","type":["list","string"]}]}}}`, | |
| "", | |
| }, | |
| { | |
| "can function marshalled correctly", | |
| map[string]function.Function{ | |
| "can": tryfunc.CanFunc, | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"can":{"return_type":"bool","parameters":[{"name":"expression","type":"dynamic"}]}}}`, | |
| "", | |
| }, | |
| { | |
| "core::can function marshalled correctly", | |
| map[string]function.Function{ | |
| "core::can": tryfunc.CanFunc, | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"core::can":{"return_type":"bool","parameters":[{"name":"expression","type":"dynamic"}]}}}`, | |
| "", | |
| }, | |
| { | |
| // This checks that if a provider contains a function named the same as one of the core with custom marshaller, we identify that correctly | |
| "provider::test::can function marshalled correctly", | |
| map[string]function.Function{ | |
| "provider::test::can": function.New(&function.Spec{ | |
| Params: []function.Parameter{ | |
| { | |
| Name: "list", | |
| Type: cty.List(cty.String), | |
| }, | |
| }, | |
| Type: function.StaticReturnType(cty.List(cty.String)), | |
| }), | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"provider::test::can":{"return_type":["list","string"],"parameters":[{"name":"list","type":["list","string"]}]}}}`, | |
| "", | |
| }, | |
| { | |
| "convert function marshalled correctly", | |
| map[string]function.Function{ | |
| "convert": funcs["convert"], | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"convert":{"description":"` + "`" + `convert` + "`" + ` attempts to convert the given value to match the given target type constraint.","return_type":"dynamic","parameters":[{"name":"value","description":"The value to convert.","is_nullable":true,"type":"dynamic"},{"name":"type","description":"The type constraint to convert to.","type":"type"}]}}}`, | |
| "", | |
| }, | |
| { | |
| "core::convert function marshalled correctly", | |
| map[string]function.Function{ | |
| "core::convert": funcs["convert"], | |
| }, | |
| `{"format_version":"1.0","function_signatures":{"core::convert":{"description":"` + "`" + `convert` + "`" + ` attempts to convert the given value to match the given target type constraint.","return_type":"dynamic","parameters":[{"name":"value","description":"The value to convert.","is_nullable":true,"type":"dynamic"},{"name":"type","description":"The type constraint to convert to.","type":"type"}]}}}`, | |
| "", | |
| }, | |
| } | |
| for i, test := range tests { | |
| t.Run(fmt.Sprintf("%d-%s", i, test.Name), func(t *testing.T) { | |
| got, diags := Marshal(test.Input) | |
| if test.WantErr != "" { | |
| if !diags.HasErrors() { | |
| t.Fatal("expected error, got none") | |
| } | |
| if diags.Err().Error() != test.WantErr { | |
| t.Fatalf("expected error %q, got %q", test.WantErr, diags.Err()) | |
| } | |
| } else { | |
| if diags.HasErrors() { | |
| t.Fatal(diags) | |
| } | |
| if diff := cmp.Diff(test.Want, string(got), ctydebug.CmpOptions); diff != "" { | |
| t.Fatalf("mismatch of function signature: %s", diff) | |
| } | |
| } | |
| }) | |
| } | |
| } |