diff --git a/cmd/ndc-go-sdk/README.md b/cmd/ndc-go-sdk/README.md index 825842b..35c0c2e 100644 --- a/cmd/ndc-go-sdk/README.md +++ b/cmd/ndc-go-sdk/README.md @@ -40,6 +40,9 @@ Commands: generate Generate schema and implementation for the connector from functions. + + test snapshots + Generate test snapshots. ``` ### Initialize connector project @@ -292,6 +295,43 @@ func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *C // } ``` -### Example +## Example See [example/codegen](../../example/codegen). + +## Test Snapshots + +The tool supports test snapshots generation for query and mutation requests and responses that are compatible with [ndc-test replay](https://github.com/hasura/ndc-spec/tree/main/ndc-test#custom-tests) command. See generated snapshots at [the codegen example](../../example/codegen/testdata). + +```bash +Usage: hasura-ndc-go test snapshots + +Generate test snapshots. + +Flags: + -h, --help Show context-sensitive help. + --log-level="info" Log level. + + --schema=STRING NDC schema file path. Use either endpoint or schema path + --endpoint=STRING The endpoint of the connector. Use either endpoint or schema path + --dir=STRING The directory of test snapshots. + --depth=10 The selection depth of nested fields in result types. + --seed=SEED Using a fixed seed will produce the same output on every run. + --query=QUERY,... Specify individual queries to be generated. Separated by commas, or 'all' for all queries + --mutation=MUTATION,... Specify individual mutations to be generated. Separated by commas, or 'all' for all mutations + --strategy="none" Decide the strategy to do when the snapshot file exists. +``` + +The command accepts either a connector `--endpoint` or a JSON `--schema` file. + +**Endpoint** + +```bash +hasura-ndc-go test snapshots --endpoint http://localhost:8080 --dir testdata +``` + +**NDC Schema** + +```bash +hasura-ndc-go test snapshots --schema schema.generated.json --dir testdata +``` diff --git a/cmd/ndc-go-sdk/command/internal/file.go b/cmd/ndc-go-sdk/command/internal/file.go index c7ad5e1..24834ce 100644 --- a/cmd/ndc-go-sdk/command/internal/file.go +++ b/cmd/ndc-go-sdk/command/internal/file.go @@ -2,13 +2,41 @@ package internal import ( "encoding/json" + "fmt" "os" + "slices" ) +// Decide the strategy to do when the written file exists +type WriteFileStrategy string + +const ( + WriteFileStrategyNone WriteFileStrategy = "none" + WriteFileStrategyOverride WriteFileStrategy = "override" +) + +var enumValues_WriteFileStrategy = []WriteFileStrategy{ + WriteFileStrategyNone, + WriteFileStrategyOverride, +} + +// ParseWriteFileStrategy parses a WriteFileStrategy enum from string +func ParseWriteFileStrategy(input string) (WriteFileStrategy, error) { + result := WriteFileStrategy(input) + if !slices.Contains(enumValues_WriteFileStrategy, result) { + return WriteFileStrategy(""), fmt.Errorf("failed to parse WriteFileStrategy, expect one of %v, got: %s", enumValues_WriteFileStrategy, input) + } + + return result, nil +} + // WritePrettyFileJSON writes JSON data with indent -func WritePrettyFileJSON(fileName string, data any) error { +func WritePrettyFileJSON(fileName string, data any, strategy WriteFileStrategy) error { if _, err := os.Stat(fileName); err == nil { - return nil + switch strategy { + case WriteFileStrategyNone, "": + return nil + } } rawBytes, err := json.MarshalIndent(data, "", " ") if err != nil { diff --git a/cmd/ndc-go-sdk/command/test_snapshots.go b/cmd/ndc-go-sdk/command/test_snapshots.go index 64262c7..c65664b 100644 --- a/cmd/ndc-go-sdk/command/test_snapshots.go +++ b/cmd/ndc-go-sdk/command/test_snapshots.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path" + "slices" "time" "github.com/hasura/ndc-sdk-go/cmd/ndc-go-sdk/command/internal" @@ -16,11 +17,14 @@ import ( // GenTestSnapshotArguments represents arguments for test snapshot generation type GenTestSnapshotArguments struct { - Schema string `help:"NDC schema file path. Use either endpoint or schema path"` - Endpoint string `help:"The endpoint of the connector. Use either endpoint or schema path"` - Dir string `help:"The directory of test snapshots."` - Depth uint `help:"The selection depth of nested fields in result types." default:"10"` - Seed *int64 `help:"Using a fixed seed will produce the same output on every run."` + Schema string `help:"NDC schema file path. Use either endpoint or schema path"` + Endpoint string `help:"The endpoint of the connector. Use either endpoint or schema path"` + Dir string `help:"The directory of test snapshots."` + Depth uint `help:"The selection depth of nested fields in result types." default:"10"` + Seed *int64 `help:"Using a fixed seed will produce the same output on every run."` + Query []string `help:"Specify individual queries to be generated. Separated by commas, or 'all' for all queries"` + Mutation []string `help:"Specify individual mutations to be generated. Separated by commas, or 'all' for all mutations"` + Strategy internal.WriteFileStrategy `help:"Decide the strategy to do when the snapshot file exists. Accept: none, override" enum:"none,override" default:"none"` } // genTestSnapshotsCommand @@ -102,7 +106,9 @@ func (cmd *genTestSnapshotsCommand) fetchSchema() error { } func (cmd *genTestSnapshotsCommand) genFunction(fn *schema.FunctionInfo) error { - + if !cmd.hasQuery(fn.Name) { + return nil + } args, err := cmd.genQueryArguments(fn.Arguments) if err != nil { return fmt.Errorf("failed to generate arguments for %s function: %s", fn.Name, err) @@ -138,11 +144,11 @@ func (cmd *genTestSnapshotsCommand) genFunction(fn *schema.FunctionInfo) error { return err } - if err := internal.WritePrettyFileJSON(path.Join(snapshotDir, "request.json"), queryReq); err != nil { + if err := internal.WritePrettyFileJSON(path.Join(snapshotDir, "request.json"), queryReq, cmd.args.Strategy); err != nil { return err } - return internal.WritePrettyFileJSON(path.Join(snapshotDir, "expected.json"), queryResp) + return internal.WritePrettyFileJSON(path.Join(snapshotDir, "expected.json"), queryResp, cmd.args.Strategy) } func (cmd *genTestSnapshotsCommand) genQueryArguments(arguments schema.FunctionInfoArguments) (schema.QueryRequestArguments, error) { @@ -161,6 +167,9 @@ func (cmd *genTestSnapshotsCommand) genQueryArguments(arguments schema.FunctionI } func (cmd *genTestSnapshotsCommand) genProcedure(proc *schema.ProcedureInfo) error { + if !cmd.hasMutation(proc.Name) { + return nil + } args, err := cmd.genOperationArguments(proc.Arguments) if err != nil { return fmt.Errorf("failed to generate arguments for %s procedure: %s", proc.Name, err) @@ -197,11 +206,11 @@ func (cmd *genTestSnapshotsCommand) genProcedure(proc *schema.ProcedureInfo) err return err } - if err := internal.WritePrettyFileJSON(path.Join(snapshotDir, "request.json"), mutationReq); err != nil { + if err := internal.WritePrettyFileJSON(path.Join(snapshotDir, "request.json"), mutationReq, cmd.args.Strategy); err != nil { return err } - return internal.WritePrettyFileJSON(path.Join(snapshotDir, "expected.json"), mutationResp) + return internal.WritePrettyFileJSON(path.Join(snapshotDir, "expected.json"), mutationResp, cmd.args.Strategy) } func (cmd *genTestSnapshotsCommand) genOperationArguments(arguments schema.ProcedureInfoArguments) ([]byte, error) { @@ -236,7 +245,7 @@ func (cmd *genTestSnapshotsCommand) genNestFieldAndValueInternal(rawType schema. if err != nil { return nil, nil, false, err } - if isScalar { + if innerType == nil || isScalar { return nil, []any{data}, isScalar, nil } return schema.NewNestedArray(innerType), []any{data}, isScalar, nil @@ -262,8 +271,25 @@ func (cmd *genTestSnapshotsCommand) genNestFieldAndValueInternal(rawType schema. fields[key] = schema.NewColumnField(key, innerType) values[key] = value } + if len(fields) == 0 { + return nil, values, false, nil + } return schema.NewNestedObject(fields), values, false, nil default: return nil, nil, false, err } } + +func (cmd genTestSnapshotsCommand) hasQuery(name string) bool { + if (len(cmd.args.Query) == 0 && len(cmd.args.Mutation) == 0) || slices.Contains(cmd.args.Query, "all") { + return true + } + return slices.Contains(cmd.args.Query, name) +} + +func (cmd genTestSnapshotsCommand) hasMutation(name string) bool { + if (len(cmd.args.Query) == 0 && len(cmd.args.Mutation) == 0) || slices.Contains(cmd.args.Mutation, "all") { + return true + } + return slices.Contains(cmd.args.Mutation, name) +} diff --git a/cmd/ndc-go-sdk/schema.go b/cmd/ndc-go-sdk/schema.go index 19aaf52..47b38cd 100644 --- a/cmd/ndc-go-sdk/schema.go +++ b/cmd/ndc-go-sdk/schema.go @@ -19,33 +19,108 @@ import ( "golang.org/x/tools/go/packages" ) -var defaultScalarTypes = schema.SchemaResponseScalarTypes{ - "String": schema.ScalarType{ +type ScalarName string + +const ( + ScalarBoolean ScalarName = "Boolean" + ScalarString ScalarName = "String" + ScalarInt8 ScalarName = "Int8" + ScalarInt16 ScalarName = "Int16" + ScalarInt32 ScalarName = "Int32" + ScalarInt64 ScalarName = "Int64" + ScalarFloat32 ScalarName = "Float32" + ScalarFloat64 ScalarName = "Float64" + ScalarBigDecimal ScalarName = "BigDecimal" + ScalarUUID ScalarName = "UUID" + ScalarDate ScalarName = "Date" + ScalarTimestamp ScalarName = "Timestamp" + ScalarTimestampTZ ScalarName = "TimestampTZ" + ScalarGeography ScalarName = "Geography" + ScalarBytes ScalarName = "Bytes" + ScalarJSON ScalarName = "JSON" +) + +var defaultScalarTypes = map[ScalarName]schema.ScalarType{ + ScalarBoolean: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationBoolean().Encode(), + }, + ScalarString: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, Representation: schema.NewTypeRepresentationString().Encode(), }, - "Int": schema.ScalarType{ + ScalarInt8: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, - Representation: schema.NewTypeRepresentationInteger().Encode(), + Representation: schema.NewTypeRepresentationInt8().Encode(), }, - "Float": schema.ScalarType{ + ScalarInt16: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, - Representation: schema.NewTypeRepresentationNumber().Encode(), + Representation: schema.NewTypeRepresentationInt16().Encode(), }, - "Boolean": schema.ScalarType{ + ScalarInt32: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, - Representation: schema.NewTypeRepresentationBoolean().Encode(), + Representation: schema.NewTypeRepresentationInt32().Encode(), }, - "UUID": schema.ScalarType{ + ScalarInt64: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, - Representation: schema.NewTypeRepresentationString().Encode(), + Representation: schema.NewTypeRepresentationInt64().Encode(), + }, + ScalarFloat32: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationFloat32().Encode(), + }, + ScalarFloat64: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationFloat64().Encode(), + }, + ScalarBigDecimal: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationBigDecimal().Encode(), + }, + ScalarUUID: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationUUID().Encode(), + }, + ScalarDate: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationDate().Encode(), + }, + ScalarTimestamp: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationTimestamp().Encode(), + }, + ScalarTimestampTZ: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationTimestampTZ().Encode(), + }, + ScalarGeography: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationGeography().Encode(), + }, + ScalarBytes: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationBytes().Encode(), + }, + ScalarJSON: { + AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, + ComparisonOperators: map[string]schema.ComparisonOperatorDefinition{}, + Representation: schema.NewTypeRepresentationJSON().Encode(), }, - "DateTime": *schema.NewScalarType(), } var ndcOperationNameRegex = regexp.MustCompile(`^(Function|Procedure)([A-Z][A-Za-z0-9]*)$`) @@ -487,30 +562,30 @@ func (sp *SchemaParser) parseType(rawSchema *RawConnectorSchema, rootType *TypeI if innerPkg != nil { typeInfo.PackageName = innerPkg.Name() typeInfo.PackagePath = innerPkg.Path() - var scalarName string + var scalarName ScalarName switch innerPkg.Path() { case "time": switch innerType.Name() { case "Time": - scalarName = "DateTime" + scalarName = ScalarTimestampTZ case "Duration": return nil, errors.New("unsupported type time.Duration. Create a scalar type wrapper with FromValue method to decode the any value") } case "github.com/google/uuid": switch innerType.Name() { case "UUID": - scalarName = "UUID" + scalarName = ScalarUUID } } if scalarName != "" { if scalar, ok := defaultScalarTypes[scalarName]; ok { - rawSchema.ScalarSchemas[scalarName] = scalar + rawSchema.ScalarSchemas[string(scalarName)] = scalar } else { - rawSchema.ScalarSchemas[scalarName] = *schema.NewScalarType() + rawSchema.ScalarSchemas[string(scalarName)] = *schema.NewScalarType() } typeInfo.IsScalar = true - typeInfo.Schema = schema.NewNamedType(scalarName) + typeInfo.Schema = schema.NewNamedType(string(scalarName)) return typeInfo, nil } } @@ -527,20 +602,32 @@ func (sp *SchemaParser) parseType(rawSchema *RawConnectorSchema, rootType *TypeI return sp.parseType(rawSchema, typeInfo, innerType.Type().Underlying(), append(fieldPaths, innerType.Name()), false) case *types.Basic: - var scalarName string + var scalarName ScalarName switch inferredType.Kind() { case types.Bool: - scalarName = "Boolean" - rawSchema.ScalarSchemas[scalarName] = defaultScalarTypes[scalarName] - case types.Int, types.Int8, types.Int16, types.Int32, types.Int64, types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64: - scalarName = "Int" - rawSchema.ScalarSchemas[scalarName] = defaultScalarTypes[scalarName] - case types.Float32, types.Float64: - scalarName = "Float" - rawSchema.ScalarSchemas[scalarName] = defaultScalarTypes[scalarName] + scalarName = ScalarBoolean + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Int8, types.Uint8: + scalarName = ScalarInt8 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Int16, types.Uint16: + scalarName = ScalarInt16 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Int, types.Int32, types.Uint, types.Uint32: + scalarName = ScalarInt32 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Int64, types.Uint64: + scalarName = ScalarInt64 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Float32: + scalarName = ScalarFloat32 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] + case types.Float64: + scalarName = ScalarFloat64 + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] case types.String: - scalarName = "String" - rawSchema.ScalarSchemas[scalarName] = defaultScalarTypes[scalarName] + scalarName = ScalarString + rawSchema.ScalarSchemas[string(scalarName)] = defaultScalarTypes[scalarName] default: return nil, fmt.Errorf("unsupported scalar type: %s", inferredType.String()) } @@ -553,7 +640,7 @@ func (sp *SchemaParser) parseType(rawSchema *RawConnectorSchema, rootType *TypeI } } - rootType.Schema = schema.NewNamedType(scalarName) + rootType.Schema = schema.NewNamedType(string(scalarName)) rootType.IsScalar = true return rootType, nil diff --git a/cmd/ndc-go-sdk/testdata/basic/expected/schema.json b/cmd/ndc-go-sdk/testdata/basic/expected/schema.json index 9f70337..32feda7 100644 --- a/cmd/ndc-go-sdk/testdata/basic/expected/schema.json +++ b/cmd/ndc-go-sdk/testdata/basic/expected/schema.json @@ -95,7 +95,7 @@ }, "Float32": { "type": { - "name": "Float", + "name": "Float32", "type": "named" } }, @@ -103,14 +103,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float32", "type": "named" } } }, "Float64": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } }, @@ -118,20 +118,20 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float64", "type": "named" } } }, "Int": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Int16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -139,14 +139,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Int32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -154,14 +154,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Int64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -169,14 +169,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Int8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -184,7 +184,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -193,7 +193,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -281,7 +281,7 @@ }, "Time": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -289,7 +289,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } } @@ -320,13 +320,13 @@ }, "Uint": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Uint16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -334,14 +334,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Uint32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -349,14 +349,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Uint64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -364,14 +364,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Uint8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -379,7 +379,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -388,7 +388,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -419,7 +419,7 @@ "arguments": { "Limit": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } } @@ -440,7 +440,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -456,7 +456,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -481,7 +481,7 @@ }, "id": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -491,13 +491,13 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, "id": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -610,7 +610,7 @@ }, "Float32": { "type": { - "name": "Float", + "name": "Float32", "type": "named" } }, @@ -618,14 +618,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float32", "type": "named" } } }, "Float64": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } }, @@ -633,20 +633,20 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float64", "type": "named" } } }, "Int": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Int16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -654,14 +654,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Int32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -669,14 +669,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Int64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -684,14 +684,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Int8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -699,7 +699,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -708,7 +708,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -796,7 +796,7 @@ }, "Time": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -804,7 +804,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } } @@ -835,13 +835,13 @@ }, "Uint": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Uint16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -849,14 +849,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Uint32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -864,14 +864,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Uint64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -879,14 +879,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Uint8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -894,7 +894,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -903,7 +903,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -934,7 +934,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -950,13 +950,13 @@ "fields": { "Lat": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Long": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -978,7 +978,7 @@ }, "num": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -1016,7 +1016,7 @@ "description": "Increase", "name": "increase", "result_type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -1081,26 +1081,58 @@ "aggregate_functions": {}, "comparison_operators": {} }, - "DateTime": { + "Float32": { "aggregate_functions": {}, - "comparison_operators": {} + "comparison_operators": {}, + "representation": { + "type": "float32" + } }, - "Float": { + "Float64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "number" + "type": "float64" } }, "Foo": { "aggregate_functions": {}, "comparison_operators": {} }, - "Int": { + "Int16": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "integer" + "type": "int16" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "Int8": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int8" + } + }, + "SomeEnum": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": ["foo", "bar"], + "type": "enum" } }, "String": { @@ -1110,19 +1142,18 @@ "type": "string" } }, - "UUID": { + "TimestampTZ": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "string" + "type": "timestamptz" } }, - "SomeEnum": { + "UUID": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "enum", - "one_of": ["foo", "bar"] + "type": "uuid" } } } diff --git a/example/codegen/functions/comment.go b/example/codegen/functions/comment.go index ec5b010..0a16b35 100644 --- a/example/codegen/functions/comment.go +++ b/example/codegen/functions/comment.go @@ -70,6 +70,7 @@ type CreateArticleArguments struct { type Author struct { ID string `json:"id"` CreatedAt time.Time `json:"created_at"` + Tags []string `json:"tags"` } type CreateArticleResult struct { diff --git a/example/codegen/functions/types.generated.go b/example/codegen/functions/types.generated.go index 81012e8..31626db 100644 --- a/example/codegen/functions/types.generated.go +++ b/example/codegen/functions/types.generated.go @@ -318,6 +318,7 @@ func (j Author) ToMap() map[string]any { r := make(map[string]any) r["created_at"] = j.CreatedAt r["id"] = j.ID + r["tags"] = j.Tags return r } diff --git a/example/codegen/schema.generated.json b/example/codegen/schema.generated.json index c34450c..62c5ef4 100644 --- a/example/codegen/schema.generated.json +++ b/example/codegen/schema.generated.json @@ -80,7 +80,7 @@ }, "Float32": { "type": { - "name": "Float", + "name": "Float32", "type": "named" } }, @@ -88,14 +88,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float32", "type": "named" } } }, "Float64": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } }, @@ -103,20 +103,20 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float64", "type": "named" } } }, "Int": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Int16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -124,14 +124,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Int32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -139,14 +139,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Int64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -154,14 +154,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Int8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -169,7 +169,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -178,7 +178,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -266,7 +266,7 @@ }, "Time": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -274,7 +274,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } } @@ -305,13 +305,13 @@ }, "Uint": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Uint16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -319,14 +319,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Uint32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -334,14 +334,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Uint64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -349,14 +349,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Uint8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -364,7 +364,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -373,7 +373,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -404,7 +404,7 @@ "arguments": { "Limit": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } } @@ -425,7 +425,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -434,6 +434,15 @@ "name": "String", "type": "named" } + }, + "tags": { + "type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } } } }, @@ -441,7 +450,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -466,7 +475,7 @@ }, "id": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -476,13 +485,13 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, "id": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -580,7 +589,7 @@ }, "Float32": { "type": { - "name": "Float", + "name": "Float32", "type": "named" } }, @@ -588,14 +597,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float32", "type": "named" } } }, "Float64": { "type": { - "name": "Float", + "name": "Float64", "type": "named" } }, @@ -603,20 +612,20 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Float", + "name": "Float64", "type": "named" } } }, "Int": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Int16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -624,14 +633,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Int32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -639,14 +648,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Int64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -654,14 +663,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Int8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -669,7 +678,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -678,7 +687,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -766,7 +775,7 @@ }, "Time": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -774,7 +783,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } } @@ -805,13 +814,13 @@ }, "Uint": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Uint16": { "type": { - "name": "Int", + "name": "Int16", "type": "named" } }, @@ -819,14 +828,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int16", "type": "named" } } }, "Uint32": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -834,14 +843,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } }, "Uint64": { "type": { - "name": "Int", + "name": "Int64", "type": "named" } }, @@ -849,14 +858,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int64", "type": "named" } } }, "Uint8": { "type": { - "name": "Int", + "name": "Int8", "type": "named" } }, @@ -864,7 +873,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int8", "type": "named" } } @@ -873,7 +882,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -904,7 +913,7 @@ "fields": { "created_at": { "type": { - "name": "DateTime", + "name": "TimestampTZ", "type": "named" } }, @@ -920,13 +929,13 @@ "fields": { "Lat": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, "Long": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } } @@ -948,7 +957,7 @@ }, "num": { "type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -986,7 +995,7 @@ "description": "Increase", "name": "increase", "result_type": { - "name": "Int", + "name": "Int32", "type": "named" } }, @@ -1047,26 +1056,50 @@ "type": "string" } }, - "DateTime": { + "Float32": { "aggregate_functions": {}, - "comparison_operators": {} + "comparison_operators": {}, + "representation": { + "type": "float32" + } }, - "Float": { + "Float64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "number" + "type": "float64" } }, "Foo": { "aggregate_functions": {}, "comparison_operators": {} }, - "Int": { + "Int16": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int16" + } + }, + "Int32": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "integer" + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "Int8": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int8" } }, "SomeEnum": { @@ -1087,11 +1120,18 @@ "type": "string" } }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + }, "UUID": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "string" + "type": "uuid" } } } diff --git a/example/codegen/testdata/query/getTypes/expected.json b/example/codegen/testdata/query/getTypes/expected.json index bb9c6dd..c2de1f0 100644 --- a/example/codegen/testdata/query/getTypes/expected.json +++ b/example/codegen/testdata/query/getTypes/expected.json @@ -5,83 +5,97 @@ "__value": { "ArrayObject": [ { - "content": "W6tI1Y40Lw" + "content": "5klHpNEnbn" } ], "ArrayObjectPtr": [ { - "content": "vQcd2I8kdI" + "content": "lHfcy8uuwg" } ], - "Bool": true, + "Bool": false, "BoolPtr": false, - "CustomScalar": "3B1e7lHaWW", - "CustomScalarPtr": "KHGzAeLfav", + "CustomScalar": "5WsGgItdvs", + "CustomScalarPtr": "jb3pMrMTE3", "Enum": "bar", - "EnumPtr": "foo", - "Float32": 0.86, - "Float32Ptr": 0.07, - "Float64": 0.681, - "Float64Ptr": 0.5753, - "Int": 531654, - "Int16": 7892, - "Int16Ptr": 1587, - "Int32": 774617, - "Int32Ptr": 1714093, - "Int64": 74910766607, - "Int64Ptr": 577626996545, - "Int8": 49, - "Int8Ptr": 51, - "IntPtr": 91294328, + "EnumPtr": "bar", + "Float32": 1.5432291, + "Float32Ptr": 4.828557, + "Float64": 9.191269, + "Float64Ptr": 0.78618646, + "Int": 6961, + "Int16": 30311, + "Int16Ptr": 19147, + "Int32": 12511, + "Int32Ptr": 26621, + "Int64": 8302, + "Int64Ptr": 12711, + "Int8": 10611, + "Int8Ptr": 26379, + "IntPtr": 13427, "NamedArray": [ { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "nCCSDfMePH" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "27vZS3wlx2", + "tags": [ + "UBnQmXNf45" + ] } ], "NamedArrayPtr": [ { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "bPgG5cs38N" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "5oihlV8a5G", + "tags": [ + "eI8YUHDp1d" + ] } ], "NamedObject": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "kxe0Qu0hZb" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "uA1Q1KvrTn", + "tags": [ + "X1i5KVSw2Z" + ] }, "NamedObjectPtr": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "2J1NWIWvJW" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "OhKLrI4xsh", + "tags": [ + "rTH8sbn1Nr" + ] }, "Object": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "2a5018a6-64cd-442c-bcca-c09dba2bf3dc" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "68fc41fe-0aa5-4541-96c0-ed43fae4a940" }, "ObjectPtr": { - "Lat": 313800, - "Long": 5913390 + "Lat": 31954, + "Long": 1580 }, - "String": "M9CdDc19mD", - "StringPtr": "FgofU1okdL", - "Text": "Tzoe8bDE5R", - "TextPtr": "jsbVTPYPzO", - "Time": "2024-03-31T11:20:13+07:00", - "TimePtr": "2024-03-31T11:20:13+07:00", - "UUID": "5084a431-3b54-472e-84d7-308aee7f6a08", - "UUIDArray": ["63cb0ca2-14c1-44bb-a2f9-72af0ca95502"], - "UUIDPtr": "361452a2-b3dc-4302-bb08-5de92d9a4879", - "Uint": 41818, - "Uint16": 8072, - "Uint16Ptr": 1587, - "Uint32": 77461, - "Uint32Ptr": 38032, - "Uint64": 51144554072184, - "Uint64Ptr": 46014897900881, - "Uint8": 68, - "Uint8Ptr": 56, - "UintPtr": 602518 + "String": "hlY9YwQzIz", + "StringPtr": "ehsQODL7yV", + "Text": "1aGSQ9t8ot", + "TextPtr": "DOwOsigKYT", + "Time": "2024-04-02T09:46:55+07:00", + "TimePtr": "2024-04-02T09:46:55+07:00", + "UUID": "43c6415b-3f30-4a7f-aa0c-f927f748af52", + "UUIDArray": [ + "8a99bd9d-e798-4d83-8f45-852df937107f" + ], + "UUIDPtr": "b2b0d44b-42fc-4486-90d1-cc1c3d848bd5", + "Uint": 13041, + "Uint16": 18289, + "Uint16Ptr": 2545, + "Uint32": 22902, + "Uint32Ptr": 8697, + "Uint64": 12399, + "Uint64Ptr": 24023, + "Uint8": 10904, + "Uint8Ptr": 18557, + "UintPtr": 22785 } } ] } -] +] \ No newline at end of file diff --git a/example/codegen/testdata/query/getTypes/request.json b/example/codegen/testdata/query/getTypes/request.json index 843c83a..b57fa8f 100644 --- a/example/codegen/testdata/query/getTypes/request.json +++ b/example/codegen/testdata/query/getTypes/request.json @@ -4,7 +4,7 @@ "type": "literal", "value": [ { - "content": "W6tI1Y40Lw" + "content": "CFrMDPyi0g" } ] }, @@ -12,7 +12,7 @@ "type": "literal", "value": [ { - "content": "vQcd2I8kdI" + "content": "MgUSnz6rq4" } ] }, @@ -22,15 +22,15 @@ }, "BoolPtr": { "type": "literal", - "value": false + "value": true }, "CustomScalar": { "type": "literal", - "value": "3B1e7lHaWW" + "value": "vMq0VC5rMu" }, "CustomScalarPtr": { "type": "literal", - "value": "KHGzAeLfav" + "value": "ALKudW2G9y" }, "Enum": { "type": "literal", @@ -42,66 +42,69 @@ }, "Float32": { "type": "literal", - "value": 0.86 + "value": 2.5860941 }, "Float32Ptr": { "type": "literal", - "value": 0.07 + "value": 13.826611 }, "Float64": { "type": "literal", - "value": 0.681 + "value": 6.2129726 }, "Float64Ptr": { "type": "literal", - "value": 0.5753 + "value": 11.418405 }, "Int": { "type": "literal", - "value": 531654 + "value": 21153 }, "Int16": { "type": "literal", - "value": 7892 + "value": 18507 }, "Int16Ptr": { "type": "literal", - "value": 1587 + "value": 20226 }, "Int32": { "type": "literal", - "value": 774617 + "value": 9411 }, "Int32Ptr": { "type": "literal", - "value": 1714093 + "value": 22963 }, "Int64": { "type": "literal", - "value": 74910766607 + "value": 19441 }, "Int64Ptr": { "type": "literal", - "value": 577626996545 + "value": 19216 }, "Int8": { "type": "literal", - "value": 49 + "value": 28349 }, "Int8Ptr": { "type": "literal", - "value": 51 + "value": 715 }, "IntPtr": { "type": "literal", - "value": 91294328 + "value": 23201 }, "NamedArray": { "type": "literal", "value": [ { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "nCCSDfMePH" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "yseCy8BnMe", + "tags": [ + "9G8XC8N9IE" + ] } ] }, @@ -109,114 +112,125 @@ "type": "literal", "value": [ { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "bPgG5cs38N" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "CcsTm4aoM9", + "tags": [ + "KRgW8tBxXr" + ] } ] }, "NamedObject": { "type": "literal", "value": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "kxe0Qu0hZb" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "tcm1eKV2NR", + "tags": [ + "S2s5tLkmRq" + ] } }, "NamedObjectPtr": { "type": "literal", "value": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "2J1NWIWvJW" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "i6EkTKT9rH", + "tags": [ + "t439K8fRMe" + ] } }, "Object": { "type": "literal", "value": { - "created_at": "2024-03-31T11:20:13+07:00", - "id": "2a5018a6-64cd-442c-bcca-c09dba2bf3dc" + "created_at": "2024-04-02T09:46:55+07:00", + "id": "7fed3e7e-9d3d-4993-a709-5950e74ff7e5" } }, "ObjectPtr": { "type": "literal", "value": { - "Lat": 313800, - "Long": 5913390 + "Lat": 116, + "Long": 28399 } }, "String": { "type": "literal", - "value": "M9CdDc19mD" + "value": "Cp9vvlBl8i" }, "StringPtr": { "type": "literal", - "value": "FgofU1okdL" + "value": "PNedoETLfu" }, "Text": { "type": "literal", - "value": "Tzoe8bDE5R" + "value": "krpxL85f2S" }, "TextPtr": { "type": "literal", - "value": "jsbVTPYPzO" + "value": "6GD9b6srAj" }, "Time": { "type": "literal", - "value": "2024-03-31T11:20:13+07:00" + "value": "2024-04-02T09:46:55+07:00" }, "TimePtr": { "type": "literal", - "value": "2024-03-31T11:20:13+07:00" + "value": "2024-04-02T09:46:55+07:00" }, "UUID": { "type": "literal", - "value": "5084a431-3b54-472e-84d7-308aee7f6a08" + "value": "a6b8dbd1-3062-4509-a11d-21972e834178" }, "UUIDArray": { "type": "literal", - "value": ["63cb0ca2-14c1-44bb-a2f9-72af0ca95502"] + "value": [ + "69f7e023-b412-4a8a-8be8-02f72c0748dd" + ] }, "UUIDPtr": { "type": "literal", - "value": "361452a2-b3dc-4302-bb08-5de92d9a4879" + "value": "85a05883-9ed7-4c54-aaba-4a2e44ad72cb" }, "Uint": { "type": "literal", - "value": 41818 + "value": 5672 }, "Uint16": { "type": "literal", - "value": 8072 + "value": 24364 }, "Uint16Ptr": { "type": "literal", - "value": 1587 + "value": 23504 }, "Uint32": { "type": "literal", - "value": 77461 + "value": 23599 }, "Uint32Ptr": { "type": "literal", - "value": 38032 + "value": 1707 }, "Uint64": { "type": "literal", - "value": 51144554072184 + "value": 23775 }, "Uint64Ptr": { "type": "literal", - "value": 46014897900881 + "value": 3632 }, "Uint8": { "type": "literal", - "value": 68 + "value": 31167 }, "Uint8Ptr": { "type": "literal", - "value": 56 + "value": 31415 }, "UintPtr": { "type": "literal", - "value": 602518 + "value": 3051 } }, "collection": "getTypes", @@ -351,6 +365,10 @@ "id": { "column": "id", "type": "column" + }, + "tags": { + "column": "tags", + "type": "column" } }, "type": "object" @@ -371,6 +389,10 @@ "id": { "column": "id", "type": "column" + }, + "tags": { + "column": "tags", + "type": "column" } }, "type": "object" @@ -390,6 +412,10 @@ "id": { "column": "id", "type": "column" + }, + "tags": { + "column": "tags", + "type": "column" } }, "type": "object" @@ -407,6 +433,10 @@ "id": { "column": "id", "type": "column" + }, + "tags": { + "column": "tags", + "type": "column" } }, "type": "object" @@ -530,4 +560,4 @@ } } } -} +} \ No newline at end of file diff --git a/schema/extend.go b/schema/extend.go index cbb3f94..4c4d13f 100644 --- a/schema/extend.go +++ b/schema/extend.go @@ -2956,7 +2956,7 @@ func (j *NestedField) UnmarshalJSON(b []byte) error { switch ty { case NestedFieldTypeObject: rawFields, ok := raw["fields"] - if !ok { + if !ok || isNullJSON(rawFields) { return errors.New("field fields in NestedField is required for object type") } var fields map[string]Field @@ -2966,7 +2966,7 @@ func (j *NestedField) UnmarshalJSON(b []byte) error { result["fields"] = fields case NestedFieldTypeArray: rawFields, ok := raw["fields"] - if !ok { + if !ok || isNullJSON(rawFields) { return errors.New("field fields in NestedField is required for array type") } var fields NestedField diff --git a/schema/scalar.go b/schema/scalar.go index 0abb276..2e0eac0 100644 --- a/schema/scalar.go +++ b/schema/scalar.go @@ -23,11 +23,54 @@ func NewScalarType() *ScalarType { type TypeRepresentationType string const ( + // JSON booleans TypeRepresentationTypeBoolean TypeRepresentationType = "boolean" - TypeRepresentationTypeString TypeRepresentationType = "string" - TypeRepresentationTypeNumber TypeRepresentationType = "number" + // JSON booleans + TypeRepresentationTypeString TypeRepresentationType = "string" + // Any JSON number + // + // Deprecated: [Deprecate Int and Number representations] + // + // [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations + TypeRepresentationTypeNumber TypeRepresentationType = "number" + // Any JSON number, with no decimal part + // + // Deprecated: [Deprecate Int and Number representations] + // + // [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations TypeRepresentationTypeInteger TypeRepresentationType = "integer" - TypeRepresentationTypeEnum TypeRepresentationType = "enum" + // One of the specified string values + TypeRepresentationTypeEnum TypeRepresentationType = "enum" + // A 8-bit signed integer with a minimum value of -2^7 and a maximum value of 2^7 - 1 + TypeRepresentationTypeInt8 TypeRepresentationType = "int8" + // A 16-bit signed integer with a minimum value of -2^15 and a maximum value of 2^15 - 1 + TypeRepresentationTypeInt16 TypeRepresentationType = "int16" + // A 32-bit signed integer with a minimum value of -2^31 and a maximum value of 2^31 - 1 + TypeRepresentationTypeInt32 TypeRepresentationType = "int32" + // A 64-bit signed integer with a minimum value of -2^63 and a maximum value of 2^63 - 1 + TypeRepresentationTypeInt64 TypeRepresentationType = "int64" + // An IEEE-754 single-precision floating-point number + TypeRepresentationTypeFloat32 TypeRepresentationType = "float32" + // An IEEE-754 double-precision floating-point number + TypeRepresentationTypeFloat64 TypeRepresentationType = "float64" + // Arbitrary-precision decimal string + TypeRepresentationTypeBigDecimal TypeRepresentationType = "bigdecimal" + // UUID string (8-4-4-4-12) + TypeRepresentationTypeUUID TypeRepresentationType = "uuid" + // ISO 8601 date + TypeRepresentationTypeDate TypeRepresentationType = "date" + // ISO 8601 timestamp + TypeRepresentationTypeTimestamp TypeRepresentationType = "timestamp" + // ISO 8601 timestamp-with-timezone + TypeRepresentationTypeTimestampTZ TypeRepresentationType = "timestamptz" + // GeoJSON, per RFC 7946 + TypeRepresentationTypeGeography TypeRepresentationType = "geography" + // GeoJSON Geometry object, per RFC 7946 + TypeRepresentationTypeGeometry TypeRepresentationType = "geometry" + // Base64-encoded bytes + TypeRepresentationTypeBytes TypeRepresentationType = "bytes" + // Arbitrary JSON + TypeRepresentationTypeJSON TypeRepresentationType = "json" ) var enumValues_TypeRepresentationType = []TypeRepresentationType{ @@ -36,6 +79,21 @@ var enumValues_TypeRepresentationType = []TypeRepresentationType{ TypeRepresentationTypeNumber, TypeRepresentationTypeInteger, TypeRepresentationTypeEnum, + TypeRepresentationTypeInt8, + TypeRepresentationTypeInt16, + TypeRepresentationTypeInt32, + TypeRepresentationTypeInt64, + TypeRepresentationTypeFloat32, + TypeRepresentationTypeFloat64, + TypeRepresentationTypeBigDecimal, + TypeRepresentationTypeUUID, + TypeRepresentationTypeDate, + TypeRepresentationTypeTimestamp, + TypeRepresentationTypeTimestampTZ, + TypeRepresentationTypeGeography, + TypeRepresentationTypeGeometry, + TypeRepresentationTypeBytes, + TypeRepresentationTypeJSON, } // ParseTypeRepresentationType parses a TypeRepresentationType enum from string @@ -162,6 +220,10 @@ func (ty TypeRepresentation) AsString() (*TypeRepresentationString, error) { } // AsNumber tries to convert the current type to TypeRepresentationNumber +// +// Deprecated: [Deprecate Int and Number representations] +// +// [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations func (ty TypeRepresentation) AsNumber() (*TypeRepresentationNumber, error) { t, err := ty.Type() if err != nil { @@ -176,7 +238,11 @@ func (ty TypeRepresentation) AsNumber() (*TypeRepresentationNumber, error) { }, nil } -// AsInteger tries to convert the current type to TypeRepresentationNumber +// AsInteger tries to convert the current type to TypeRepresentationInteger +// +// Deprecated: [Deprecate Int and Number representations] +// +// [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations func (ty TypeRepresentation) AsInteger() (*TypeRepresentationInteger, error) { t, err := ty.Type() if err != nil { @@ -191,6 +257,231 @@ func (ty TypeRepresentation) AsInteger() (*TypeRepresentationInteger, error) { }, nil } +// AsInt8 tries to convert the current type to TypeRepresentationInt8 +func (ty TypeRepresentation) AsInt8() (*TypeRepresentationInt8, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeInt8 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeInt8, t) + } + + return &TypeRepresentationInt8{ + Type: t, + }, nil +} + +// AsInt16 tries to convert the current type to TypeRepresentationInt16 +func (ty TypeRepresentation) AsInt16() (*TypeRepresentationInt16, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeInt16 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeInt16, t) + } + + return &TypeRepresentationInt16{ + Type: t, + }, nil +} + +// AsInt32 tries to convert the current type to TypeRepresentationInt32 +func (ty TypeRepresentation) AsInt32() (*TypeRepresentationInt32, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeInt32 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeInt32, t) + } + + return &TypeRepresentationInt32{ + Type: t, + }, nil +} + +// AsInt64 tries to convert the current type to TypeRepresentationInt64 +func (ty TypeRepresentation) AsInt64() (*TypeRepresentationInt64, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeInt64 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeInt64, t) + } + + return &TypeRepresentationInt64{ + Type: t, + }, nil +} + +// AsFloat32 tries to convert the current type to TypeRepresentationFloat32 +func (ty TypeRepresentation) AsFloat32() (*TypeRepresentationFloat32, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeFloat32 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeFloat32, t) + } + + return &TypeRepresentationFloat32{ + Type: t, + }, nil +} + +// AsFloat64 tries to convert the current type to TypeRepresentationFloat64 +func (ty TypeRepresentation) AsFloat64() (*TypeRepresentationFloat64, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeFloat64 { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeFloat64, t) + } + + return &TypeRepresentationFloat64{ + Type: t, + }, nil +} + +// AsBigDecimal tries to convert the current type to TypeRepresentationBigDecimal +func (ty TypeRepresentation) AsBigDecimal() (*TypeRepresentationBigDecimal, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeBigDecimal { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeBigDecimal, t) + } + + return &TypeRepresentationBigDecimal{ + Type: t, + }, nil +} + +// AsUUID tries to convert the current type to TypeRepresentationUUID +func (ty TypeRepresentation) AsUUID() (*TypeRepresentationUUID, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeUUID { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeUUID, t) + } + + return &TypeRepresentationUUID{ + Type: t, + }, nil +} + +// AsDate tries to convert the current type to TypeRepresentationDate +func (ty TypeRepresentation) AsDate() (*TypeRepresentationDate, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeDate { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeDate, t) + } + + return &TypeRepresentationDate{ + Type: t, + }, nil +} + +// AsTimestamp tries to convert the current type to TypeRepresentationTimestamp +func (ty TypeRepresentation) AsTimestamp() (*TypeRepresentationTimestamp, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeTimestamp { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeTimestamp, t) + } + + return &TypeRepresentationTimestamp{ + Type: t, + }, nil +} + +// AsTimestampTZ tries to convert the current type to TypeRepresentationTimestampTZ +func (ty TypeRepresentation) AsTimestampTZ() (*TypeRepresentationTimestampTZ, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeTimestampTZ { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeTimestampTZ, t) + } + + return &TypeRepresentationTimestampTZ{ + Type: t, + }, nil +} + +// AsGeography tries to convert the current type to TypeRepresentationGeography +func (ty TypeRepresentation) AsGeography() (*TypeRepresentationGeography, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeGeography { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeGeography, t) + } + + return &TypeRepresentationGeography{ + Type: t, + }, nil +} + +// AsGeometry tries to convert the current type to TypeRepresentationGeometry +func (ty TypeRepresentation) AsGeometry() (*TypeRepresentationGeometry, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeGeometry { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeGeometry, t) + } + + return &TypeRepresentationGeometry{ + Type: t, + }, nil +} + +// AsBytes tries to convert the current type to TypeRepresentationBytes +func (ty TypeRepresentation) AsBytes() (*TypeRepresentationBytes, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeBytes { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeBytes, t) + } + + return &TypeRepresentationBytes{ + Type: t, + }, nil +} + +// AsJSON tries to convert the current type to TypeRepresentationJSON +func (ty TypeRepresentation) AsJSON() (*TypeRepresentationJSON, error) { + t, err := ty.Type() + if err != nil { + return nil, err + } + if t != TypeRepresentationTypeJSON { + return nil, fmt.Errorf("invalid TypeRepresentation type; expected %s, got %s", TypeRepresentationTypeJSON, t) + } + + return &TypeRepresentationJSON{ + Type: t, + }, nil +} + var errTypeRepresentationOneOfRequired = errors.New("TypeRepresentationEnum must have at least 1 item in one_of array") // AsEnum tries to convert the current type to TypeRepresentationEnum @@ -247,6 +538,36 @@ func (ty TypeRepresentation) InterfaceT() (TypeRepresentationEncoder, error) { return ty.AsInteger() case TypeRepresentationTypeEnum: return ty.AsEnum() + case TypeRepresentationTypeInt8: + return ty.AsInt8() + case TypeRepresentationTypeInt16: + return ty.AsInt16() + case TypeRepresentationTypeInt32: + return ty.AsInt32() + case TypeRepresentationTypeInt64: + return ty.AsInt64() + case TypeRepresentationTypeFloat32: + return ty.AsFloat32() + case TypeRepresentationTypeFloat64: + return ty.AsFloat64() + case TypeRepresentationTypeBigDecimal: + return ty.AsBigDecimal() + case TypeRepresentationTypeUUID: + return ty.AsUUID() + case TypeRepresentationTypeDate: + return ty.AsDate() + case TypeRepresentationTypeTimestamp: + return ty.AsTimestamp() + case TypeRepresentationTypeTimestampTZ: + return ty.AsTimestampTZ() + case TypeRepresentationTypeGeography: + return ty.AsGeography() + case TypeRepresentationTypeGeometry: + return ty.AsGeometry() + case TypeRepresentationTypeBytes: + return ty.AsBytes() + case TypeRepresentationTypeJSON: + return ty.AsJSON() default: return nil, fmt.Errorf("invalid TypeRepresentation type: %s", t) } @@ -296,6 +617,10 @@ func (ty TypeRepresentationString) Encode() TypeRepresentation { } // TypeRepresentationNumber represents a JSON number type representation +// +// Deprecated: [Deprecate Int and Number representations] +// +// [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations type TypeRepresentationNumber struct { Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` } @@ -315,6 +640,10 @@ func (ty TypeRepresentationNumber) Encode() TypeRepresentation { } // TypeRepresentationInteger represents a JSON integer type representation +// +// Deprecated: [Deprecate Int and Number representations] +// +// [Deprecate Int and Number representations]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#deprecate-int-and-number-representations type TypeRepresentationInteger struct { Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` } @@ -333,6 +662,291 @@ func (ty TypeRepresentationInteger) Encode() TypeRepresentation { } } +// TypeRepresentationInt8 represents a 8-bit signed integer with a minimum value of -2^7 and a maximum value of 2^7 - 1 +type TypeRepresentationInt8 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationInt8 creates a new TypeRepresentationInt8 instance +func NewTypeRepresentationInt8() *TypeRepresentationInt8 { + return &TypeRepresentationInt8{ + Type: TypeRepresentationTypeInt8, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationInt8) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationInt16 represents a 16-bit signed integer with a minimum value of -2^15 and a maximum value of 2^15 - 1 +type TypeRepresentationInt16 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationInt16 creates a new TypeRepresentationInt16 instance +func NewTypeRepresentationInt16() *TypeRepresentationInt16 { + return &TypeRepresentationInt16{ + Type: TypeRepresentationTypeInt16, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationInt16) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationInt32 represents a 32-bit signed integer with a minimum value of -2^31 and a maximum value of 2^31 - 1 +type TypeRepresentationInt32 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationInt32 creates a new TypeRepresentationInt32 instance +func NewTypeRepresentationInt32() *TypeRepresentationInt32 { + return &TypeRepresentationInt32{ + Type: TypeRepresentationTypeInt32, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationInt32) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationInt64 represents a 64-bit signed integer with a minimum value of -2^63 and a maximum value of 2^63 - 1 +type TypeRepresentationInt64 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationInt64 creates a new TypeRepresentationInt64 instance +func NewTypeRepresentationInt64() *TypeRepresentationInt64 { + return &TypeRepresentationInt64{ + Type: TypeRepresentationTypeInt64, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationInt64) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationFloat32 represents an IEEE-754 single-precision floating-point number +type TypeRepresentationFloat32 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationFloat32 creates a new TypeRepresentationFloat32 instance +func NewTypeRepresentationFloat32() *TypeRepresentationFloat32 { + return &TypeRepresentationFloat32{ + Type: TypeRepresentationTypeFloat32, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationFloat32) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationFloat64 represents an IEEE-754 double-precision floating-point number +type TypeRepresentationFloat64 struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationFloat64 creates a new TypeRepresentationFloat64 instance +func NewTypeRepresentationFloat64() *TypeRepresentationFloat64 { + return &TypeRepresentationFloat64{ + Type: TypeRepresentationTypeFloat64, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationFloat64) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationBigDecimal represents an arbitrary-precision decimal string +type TypeRepresentationBigDecimal struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationBigDecimal creates a new TypeRepresentationBigDecimal instance +func NewTypeRepresentationBigDecimal() *TypeRepresentationBigDecimal { + return &TypeRepresentationBigDecimal{ + Type: TypeRepresentationTypeBigDecimal, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationBigDecimal) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationUUID represents an UUID string (8-4-4-4-12) +type TypeRepresentationUUID struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationUUID creates a new TypeRepresentationUUID instance +func NewTypeRepresentationUUID() *TypeRepresentationUUID { + return &TypeRepresentationUUID{ + Type: TypeRepresentationTypeUUID, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationUUID) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationDate represents an ISO 8601 date +type TypeRepresentationDate struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationDate creates a new TypeRepresentationDate instance +func NewTypeRepresentationDate() *TypeRepresentationDate { + return &TypeRepresentationDate{ + Type: TypeRepresentationTypeDate, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationDate) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationTimestamp represents an ISO 8601 timestamp +type TypeRepresentationTimestamp struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationTimestamp creates a new TypeRepresentationTimestamp instance +func NewTypeRepresentationTimestamp() *TypeRepresentationTimestamp { + return &TypeRepresentationTimestamp{ + Type: TypeRepresentationTypeTimestamp, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationTimestamp) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationTimestampTZ represents an ISO 8601 timestamp-with-timezone +type TypeRepresentationTimestampTZ struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationTimestampTZ creates a new TypeRepresentationTimestampTZ instance +func NewTypeRepresentationTimestampTZ() *TypeRepresentationTimestampTZ { + return &TypeRepresentationTimestampTZ{ + Type: TypeRepresentationTypeTimestampTZ, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationTimestampTZ) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationGeography represents a geography JSON object +type TypeRepresentationGeography struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationGeography creates a new TypeRepresentationGeography instance +func NewTypeRepresentationGeography() *TypeRepresentationGeography { + return &TypeRepresentationGeography{ + Type: TypeRepresentationTypeGeography, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationGeography) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationGeometry represents a geography JSON object +type TypeRepresentationGeometry struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationGeometry creates a new TypeRepresentationGeometry instance +func NewTypeRepresentationGeometry() *TypeRepresentationGeometry { + return &TypeRepresentationGeometry{ + Type: TypeRepresentationTypeGeometry, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationGeometry) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationBytes represent a base64-encoded bytes +type TypeRepresentationBytes struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationBytes creates a new TypeRepresentationBytes instance +func NewTypeRepresentationBytes() *TypeRepresentationBytes { + return &TypeRepresentationBytes{ + Type: TypeRepresentationTypeBytes, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationBytes) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + +// TypeRepresentationJSON represents an arbitrary JSON +type TypeRepresentationJSON struct { + Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` +} + +// NewTypeRepresentationJSON creates a new TypeRepresentationBytes instance +func NewTypeRepresentationJSON() *TypeRepresentationJSON { + return &TypeRepresentationJSON{ + Type: TypeRepresentationTypeJSON, + } +} + +// Encode returns the raw TypeRepresentation instance +func (ty TypeRepresentationJSON) Encode() TypeRepresentation { + return map[string]any{ + "type": ty.Type, + } +} + // TypeRepresentationEnum represents an enum type representation type TypeRepresentationEnum struct { Type TypeRepresentationType `json:"type" yaml:"type" mapstructure:"type"` diff --git a/schema/scalar_test.go b/schema/scalar_test.go index eb7ef7b..80dc932 100644 --- a/schema/scalar_test.go +++ b/schema/scalar_test.go @@ -55,8 +55,24 @@ func TestTypeRepresentation(t *testing.T) { }) t.Run("enum", func(t *testing.T) { + rawBytes := []byte(`{ + "type": "enum", + "one_of": ["foo"] + }`) + + rawEmptyBytes := []byte(`{ + "type": "enum", + "one_of": [] + }`) + + var enumType TypeRepresentation + assertError(t, json.Unmarshal(rawEmptyBytes, &enumType), "TypeRepresentation requires at least 1 item in one_of field for enum type") + + assertNoError(t, json.Unmarshal(rawBytes, &enumType)) + typeRep := NewTypeRepresentationEnum([]string{"foo"}) rawType := typeRep.Encode() + assertDeepEqual(t, enumType, rawType) _, err := rawType.AsString() assertError(t, err, "invalid TypeRepresentation type; expected string, got enum") @@ -80,6 +96,185 @@ func TestTypeRepresentation(t *testing.T) { assertError(t, err, "TypeRepresentationEnum must have at least 1 item in one_of array") }) + t.Run("int8", func(t *testing.T) { + typeRep := NewTypeRepresentationInt8() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got int8") + + anyType, ok := rawType.Interface().(*TypeRepresentationInt8) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("int16", func(t *testing.T) { + typeRep := NewTypeRepresentationInt16() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got int16") + + anyType, ok := rawType.Interface().(*TypeRepresentationInt16) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("int32", func(t *testing.T) { + typeRep := NewTypeRepresentationInt32() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got int32") + + anyType, ok := rawType.Interface().(*TypeRepresentationInt32) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("int64", func(t *testing.T) { + typeRep := NewTypeRepresentationInt64() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got int64") + + anyType, ok := rawType.Interface().(*TypeRepresentationInt64) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + t.Run("float32", func(t *testing.T) { + typeRep := NewTypeRepresentationFloat32() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got float32") + + anyType, ok := rawType.Interface().(*TypeRepresentationFloat32) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("float64", func(t *testing.T) { + typeRep := NewTypeRepresentationFloat64() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got float64") + + anyType, ok := rawType.Interface().(*TypeRepresentationFloat64) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("big decimal", func(t *testing.T) { + typeRep := NewTypeRepresentationBigDecimal() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got bigdecimal") + + anyType, ok := rawType.Interface().(*TypeRepresentationBigDecimal) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("uuid", func(t *testing.T) { + typeRep := NewTypeRepresentationUUID() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got uuid") + + anyType, ok := rawType.Interface().(*TypeRepresentationUUID) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("date", func(t *testing.T) { + typeRep := NewTypeRepresentationDate() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got date") + + anyType, ok := rawType.Interface().(*TypeRepresentationDate) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("timestamp", func(t *testing.T) { + typeRep := NewTypeRepresentationTimestamp() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got timestamp") + + anyType, ok := rawType.Interface().(*TypeRepresentationTimestamp) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("timestamptz", func(t *testing.T) { + typeRep := NewTypeRepresentationTimestampTZ() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got timestamptz") + + anyType, ok := rawType.Interface().(*TypeRepresentationTimestampTZ) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("geography", func(t *testing.T) { + typeRep := NewTypeRepresentationGeography() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got geography") + + anyType, ok := rawType.Interface().(*TypeRepresentationGeography) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("geometry", func(t *testing.T) { + typeRep := NewTypeRepresentationGeometry() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got geometry") + + anyType, ok := rawType.Interface().(*TypeRepresentationGeometry) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("bytes", func(t *testing.T) { + typeRep := NewTypeRepresentationBytes() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got bytes") + + anyType, ok := rawType.Interface().(*TypeRepresentationBytes) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + + t.Run("json", func(t *testing.T) { + typeRep := NewTypeRepresentationJSON() + rawType := typeRep.Encode() + + _, err := rawType.AsString() + assertError(t, err, "invalid TypeRepresentation type; expected string, got json") + + anyType, ok := rawType.Interface().(*TypeRepresentationJSON) + assertDeepEqual(t, true, ok) + assertDeepEqual(t, anyType, typeRep) + }) + t.Run("invalid", func(t *testing.T) { rawType := TypeRepresentation{} diff --git a/schema/schema.generated.go b/schema/schema.generated.go index 6aaa53b..a63d178 100644 --- a/schema/schema.generated.go +++ b/schema/schema.generated.go @@ -950,7 +950,8 @@ type ScalarType struct { // must be defined scalar types declared in ScalarTypesCapabilities. ComparisonOperators map[string]ComparisonOperatorDefinition `json:"comparison_operators" yaml:"comparison_operators" mapstructure:"comparison_operators"` - // An optional description of valid values for this scalar type + // A description of valid values for this scalar type. Defaults to + // `TypeRepresentation::JSON` if omitted Representation TypeRepresentation `json:"representation,omitempty" yaml:"representation,omitempty" mapstructure:"representation,omitempty"` } diff --git a/schema/schema.generated.json b/schema/schema.generated.json index e53dc73..4897959 100644 --- a/schema/schema.generated.json +++ b/schema/schema.generated.json @@ -243,7 +243,7 @@ ], "properties": { "representation": { - "description": "An optional description of valid values for this scalar type", + "description": "A description of valid values for this scalar type. Defaults to `TypeRepresentation::JSON` if omitted", "anyOf": [ { "$ref": "#/definitions/TypeRepresentation" @@ -270,7 +270,7 @@ } }, "TypeRepresentation": { - "title": "Type", + "title": "Type Representation", "description": "Representations of scalar types", "oneOf": [ { @@ -305,6 +305,7 @@ }, { "description": "Any JSON number", + "deprecated": true, "type": "object", "required": [ "type" @@ -320,6 +321,7 @@ }, { "description": "Any JSON number, with no decimal part", + "deprecated": true, "type": "object", "required": [ "type" @@ -333,6 +335,231 @@ } } }, + { + "description": "A 8-bit signed integer with a minimum value of -2^7 and a maximum value of 2^7 - 1", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "int8" + ] + } + } + }, + { + "description": "A 16-bit signed integer with a minimum value of -2^15 and a maximum value of 2^15 - 1", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "int16" + ] + } + } + }, + { + "description": "A 32-bit signed integer with a minimum value of -2^31 and a maximum value of 2^31 - 1", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "int32" + ] + } + } + }, + { + "description": "A 64-bit signed integer with a minimum value of -2^63 and a maximum value of 2^63 - 1", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "int64" + ] + } + } + }, + { + "description": "An IEEE-754 single-precision floating-point number", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "float32" + ] + } + } + }, + { + "description": "An IEEE-754 double-precision floating-point number", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "float64" + ] + } + } + }, + { + "description": "Arbitrary-precision decimal string", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "bigdecimal" + ] + } + } + }, + { + "description": "UUID string (8-4-4-4-12)", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "uuid" + ] + } + } + }, + { + "description": "ISO 8601 date", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "date" + ] + } + } + }, + { + "description": "ISO 8601 timestamp", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "timestamp" + ] + } + } + }, + { + "description": "ISO 8601 timestamp-with-timezone", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "timestamptz" + ] + } + } + }, + { + "description": "GeoJSON, per RFC 7946", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "geography" + ] + } + } + }, + { + "description": "GeoJSON Geometry object, per RFC 7946", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "geometry" + ] + } + } + }, + { + "description": "Base64-encoded bytes", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "bytes" + ] + } + } + }, + { + "description": "Arbitrary JSON", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "json" + ] + } + } + }, { "description": "One of the specified string values", "type": "object", diff --git a/utils/decode.go b/utils/decode.go index 1e3ab29..daab708 100644 --- a/utils/decode.go +++ b/utils/decode.go @@ -10,7 +10,7 @@ import ( "github.com/go-viper/mapstructure/v2" ) -type convertFunc[T any] func(value reflect.Value) (*T, error) +type convertFunc[T any] func(value any) (*T, error) // ValueDecoder abstracts a type with the FromValue method to decode any value type ValueDecoder interface { @@ -217,8 +217,8 @@ func (d Decoder) decodeValue(target any, value any) error { // DecodeNullableInt tries to convert an unknown value to a nullable integer func DecodeNullableInt[T int | int8 | int16 | int32 | int64](value any) (*T, error) { - return decodeNullableInt(value, func(v reflect.Value) (*T, error) { - rawResult, err := strconv.ParseInt(fmt.Sprint(v.Interface()), 10, 64) + return decodeNullableInt(value, func(v any) (*T, error) { + rawResult, err := strconv.ParseInt(fmt.Sprint(v), 10, 64) if err != nil { return nil, err } @@ -241,8 +241,8 @@ func DecodeInt[T int | int8 | int16 | int32 | int64](value any) (T, error) { // DecodeNullableUint tries to convert an unknown value to a nullable unsigned integer pointer func DecodeNullableUint[T uint | uint8 | uint16 | uint32 | uint64](value any) (*T, error) { - return decodeNullableInt(value, func(v reflect.Value) (*T, error) { - rawResult, err := strconv.ParseUint(fmt.Sprint(v.Interface()), 10, 64) + return decodeNullableInt(value, func(v any) (*T, error) { + rawResult, err := strconv.ParseUint(fmt.Sprint(v), 10, 64) if err != nil { return nil, err } @@ -293,6 +293,12 @@ func decodeNullableInt[T int | int8 | int16 | int32 | int64 | uint | uint8 | uin result = T(v) case float64: result = T(v) + case string: + newVal, err := convertFn(v) + if err != nil { + return nil, fmt.Errorf("failed to convert integer, got: %s", v) + } + return newVal, err case *int: result = T(*v) case *int8: @@ -317,7 +323,13 @@ func decodeNullableInt[T int | int8 | int16 | int32 | int64 | uint | uint8 | uin result = T(*v) case *float64: result = T(*v) - case bool, string, complex64, complex128, time.Time, time.Duration, time.Ticker, *bool, *string, *complex64, *complex128, *time.Time, *time.Duration, *time.Ticker, []bool, []string, []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []float32, []float64, []complex64, []complex128, []time.Time, []time.Duration, []time.Ticker: + case *string: + newVal, err := convertFn(*v) + if err != nil { + return nil, fmt.Errorf("failed to convert integer, got: %s", *v) + } + return newVal, err + case bool, complex64, complex128, time.Time, time.Duration, time.Ticker, *bool, *complex64, *complex128, *time.Time, *time.Duration, *time.Ticker, []bool, []string, []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []float32, []float64, []complex64, []complex128, []time.Time, []time.Duration, []time.Ticker: return nil, fmt.Errorf("failed to convert integer, got: %+v", value) default: inferredValue := reflect.ValueOf(value) @@ -331,8 +343,8 @@ func decodeNullableInt[T int | int8 | int16 | int32 | int64 | uint | uint8 | uin result = T(inferredValue.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: result = T(inferredValue.Uint()) - case reflect.Interface: - newVal, parseErr := convertFn(inferredValue) + case reflect.Interface, reflect.String: + newVal, parseErr := convertFn(inferredValue.Interface()) if parseErr != nil { return nil, fmt.Errorf("failed to convert integer, got: %s (%+v)", originType.String(), inferredValue.Interface()) } diff --git a/utils/decode_test.go b/utils/decode_test.go index 5bd85b6..ecd158f 100644 --- a/utils/decode_test.go +++ b/utils/decode_test.go @@ -197,7 +197,7 @@ func TestDecodeDuration(t *testing.T) { func TestDecodeInt(t *testing.T) { - for _, expected := range []any{int(1), int8(2), int16(3), int32(4), int64(5), uint(6), uint8(7), uint16(8), uint32(9), uint64(10)} { + for _, expected := range []any{int(1), int8(2), int16(3), int32(4), int64(5), uint(6), uint8(7), uint16(8), uint32(9), uint64(10), "11"} { t.Run(fmt.Sprintf("decode_%s", reflect.TypeOf(expected).String()), func(t *testing.T) { value, err := DecodeInt[int64](expected) @@ -292,13 +292,17 @@ func TestDecodeInt(t *testing.T) { func TestDecodeUint(t *testing.T) { - for _, expected := range []any{int(1), int8(2), int16(3), int32(4), int64(5), uint(6), uint8(7), uint16(8), uint32(9), uint64(10)} { + for _, expected := range []any{int(1), int8(2), int16(3), int32(4), int64(5), uint(6), uint8(7), uint16(8), uint32(9), uint64(10), "11"} { t.Run(fmt.Sprintf("decode_%s", reflect.TypeOf(expected).String()), func(t *testing.T) { value, err := DecodeUint[uint64](expected) assertNoError(t, err) assertEqual(t, fmt.Sprint(expected), fmt.Sprint(value)) + value, err = DecodeUint[uint64](expected) + assertNoError(t, err) + assertEqual(t, fmt.Sprint(expected), fmt.Sprint(value)) + ptr, err := DecodeNullableUint[uint64](&expected) assertNoError(t, err) assertEqual(t, fmt.Sprint(expected), fmt.Sprint(*ptr)) diff --git a/utils/encode.go b/utils/encode.go index 878a7a3..19715ff 100644 --- a/utils/encode.go +++ b/utils/encode.go @@ -100,12 +100,19 @@ func encodeObject(input any) (map[string]any, error) { } } +// encode fields with [type representation spec] +// +// [type representation spec]: https://github.com/hasura/ndc-spec/blob/main/rfcs/0007-additional-type-representations.md#new-representations func encodeField(input reflect.Value) (any, bool) { switch input.Kind() { case reflect.Complex64, reflect.Complex128: return nil, false - case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String: + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Float32, reflect.Float64, reflect.String: return input.Interface(), true + case reflect.Int64: + return fmt.Sprintf("%d", input.Int()), true + case reflect.Uint64: + return fmt.Sprintf("%d", input.Uint()), true case reflect.Struct: inputType := input.Type() switch inputType.PkgPath() {