Skip to content

Commit

Permalink
Change return_type & type in JSON to json.RawMessage
Browse files Browse the repository at this point in the history
This enables easier deserialisation of types when parsing the JSON.
  • Loading branch information
dbanck committed Jan 16, 2023
1 parent f7cef59 commit b3afc28
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 29 deletions.
34 changes: 27 additions & 7 deletions internal/command/jsonfunction/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jsonfunction

import (
"encoding/json"
"fmt"

"github.com/zclconf/go-cty/cty/function"
)
Expand All @@ -26,7 +27,9 @@ type FunctionSignature struct {
// ReturnTypes is the ctyjson representation of the function's
// return types based on supplying all parameters using
// dynamic types. Functions can have dynamic return types.
ReturnType string `json:"return_type"`
// TODO? could we use cty.Type here instead of calling ctyjson.MarshalType manually?
// TODO? see: https://github.com/zclconf/go-cty/blob/main/cty/json/type.go
ReturnType json.RawMessage `json:"return_type"`

// Parameters describes the function's fixed positional parameters.
Parameters []*parameter `json:"parameters,omitempty"`
Expand All @@ -48,28 +51,45 @@ func Marshal(f map[string]function.Function) ([]byte, error) {
signatures := newFunctions()

for k, v := range f {
signatures.Signatures[k] = marshalFunction(v)
signature, err := marshalFunction(v)
if err != nil {
fmt.Printf("Failed to serialize function %q: %s\n", k, err) // TODO! handle error
continue
}
signatures.Signatures[k] = signature
}

ret, err := json.Marshal(signatures)
return ret, err
}

func marshalFunction(f function.Function) *FunctionSignature {
func marshalFunction(f function.Function) (*FunctionSignature, error) {
var err error
var vp *parameter
if f.VarParam() != nil {
vp = marshalParameter(f.VarParam())
vp, err = marshalParameter(f.VarParam())
if err != nil {
return nil, err
}
}

var p []*parameter
if len(f.Params()) > 0 {
p = marshalParameters(f.Params())
p, err = marshalParameters(f.Params())
if err != nil {
return nil, err
}
}

r, err := getReturnType(f)
if err != nil {
return nil, err
}

return &FunctionSignature{
Description: f.Description(),
ReturnType: getReturnType(f),
ReturnType: r,
Parameters: p,
VariadicParameter: vp,
}
}, nil
}
41 changes: 33 additions & 8 deletions internal/command/jsonfunction/function_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonfunction

import (
"encoding/json"
"fmt"
"testing"

Expand All @@ -22,7 +23,7 @@ func TestMarshalFunction(t *testing.T) {
Type: function.StaticReturnType(cty.Bool),
}),
&FunctionSignature{
ReturnType: "bool",
ReturnType: json.RawMessage(`"bool"`),
},
},
{
Expand All @@ -33,7 +34,7 @@ func TestMarshalFunction(t *testing.T) {
}),
&FunctionSignature{
Description: "`timestamp` returns a UTC timestamp string.",
ReturnType: "string",
ReturnType: json.RawMessage(`"string"`),
},
},
{
Expand All @@ -54,17 +55,17 @@ func TestMarshalFunction(t *testing.T) {
Type: function.StaticReturnType(cty.String),
}),
&FunctionSignature{
ReturnType: "string",
ReturnType: json.RawMessage(`"string"`),
Parameters: []*parameter{
{
Name: "timestamp",
Description: "timestamp text",
Type: "string",
Type: json.RawMessage(`"string"`),
},
{
Name: "duration",
Description: "duration text",
Type: "string",
Type: json.RawMessage(`"string"`),
},
},
},
Expand All @@ -84,20 +85,44 @@ func TestMarshalFunction(t *testing.T) {
Type: function.StaticReturnType(cty.DynamicPseudoType),
}),
&FunctionSignature{
ReturnType: "dynamic",
ReturnType: json.RawMessage(`"dynamic"`),
VariadicParameter: &parameter{
Name: "default",
Description: "default description",
Type: "dynamic",
Type: json.RawMessage(`"dynamic"`),
IsNullable: true,
},
},
},
{
"function with list types",
function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.String),
},
},
Type: function.StaticReturnType(cty.List(cty.String)),
}),
&FunctionSignature{
ReturnType: json.RawMessage(`["list","string"]`),
Parameters: []*parameter{
{
Name: "list",
Type: json.RawMessage(`["list","string"]`),
},
},
},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("%d-%s", i, test.Name), func(t *testing.T) {
got := marshalFunction(test.Input)
got, err := marshalFunction(test.Input)
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(test.Want, got, ctydebug.CmpOptions); diff != "" {
t.Fatalf("mismatch of function signature: %s", diff)
Expand Down
30 changes: 22 additions & 8 deletions internal/command/jsonfunction/parameter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package jsonfunction

import (
"encoding/json"

"github.com/zclconf/go-cty/cty/function"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

// parameter represents a parameter to a function.
Expand All @@ -17,26 +20,37 @@ type parameter struct {
IsNullable bool `json:"is_nullable"`

// A type that any argument for this parameter must conform to.
Type string `json:"type"`
// TODO? could we use cty.Type here instead of calling ctyjson.MarshalType manually?
// TODO? see: https://github.com/zclconf/go-cty/blob/main/cty/json/type.go
Type json.RawMessage `json:"type"`
}

func marshalParameter(p *function.Parameter) *parameter {
func marshalParameter(p *function.Parameter) (*parameter, error) {
if p == nil {
return &parameter{}
return &parameter{}, nil
}

t, err := ctyjson.MarshalType(p.Type)
if err != nil {
return nil, err
}

return &parameter{
Name: p.Name,
Description: p.Description,
IsNullable: p.AllowNull,
Type: p.Type.FriendlyName(),
}
Type: t,
}, nil
}

func marshalParameters(parameters []function.Parameter) []*parameter {
func marshalParameters(parameters []function.Parameter) ([]*parameter, error) {
ret := make([]*parameter, len(parameters))
for k, p := range parameters {
ret[k] = marshalParameter(&p)
mp, err := marshalParameter(&p)
if err != nil {
return nil, err
}
ret[k] = mp
}
return ret
return ret, nil
}
10 changes: 7 additions & 3 deletions internal/command/jsonfunction/parameter_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonfunction

import (
"encoding/json"
"fmt"
"testing"

Expand Down Expand Up @@ -31,7 +32,7 @@ func TestMarshalParameter(t *testing.T) {
&parameter{
Name: "timestamp",
Description: "`timestamp` returns a UTC timestamp string in [RFC 3339]",
Type: "string",
Type: json.RawMessage(`"string"`),
},
},
{
Expand All @@ -46,15 +47,18 @@ func TestMarshalParameter(t *testing.T) {
},
&parameter{
Name: "value",
Type: "dynamic",
Type: json.RawMessage(`"dynamic"`),
IsNullable: true,
},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("%d-%s", i, test.Name), func(t *testing.T) {
got := marshalParameter(test.Input)
got, err := marshalParameter(test.Input)
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(test.Want, got, ctydebug.CmpOptions); diff != "" {
t.Fatalf("mismatch of parameter signature: %s", diff)
Expand Down
7 changes: 4 additions & 3 deletions internal/command/jsonfunction/return_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package jsonfunction
import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

func getReturnType(f function.Function) string {
func getReturnType(f function.Function) ([]byte, error) {
args := make([]cty.Type, 0)
for _, param := range f.Params() {
args = append(args, param.Type)
Expand All @@ -16,7 +17,7 @@ func getReturnType(f function.Function) string {

returnType, err := f.ReturnType(args)
if err != nil {
return "" // TODO? handle error
return nil, err
}
return returnType.FriendlyName()
return ctyjson.MarshalType(returnType)
}

0 comments on commit b3afc28

Please sign in to comment.