diff --git a/cmd/hasura-ndc-go/command/internal/connector.go b/cmd/hasura-ndc-go/command/internal/connector.go index 29a2b210..9930f43e 100644 --- a/cmd/hasura-ndc-go/command/internal/connector.go +++ b/cmd/hasura-ndc-go/command/internal/connector.go @@ -86,9 +86,11 @@ func ParseAndGenerateConnector(args ConnectorGenerationArguments, moduleName str if err != nil { return fmt.Errorf("failed to create trace file at %s", args.Trace) } + defer func() { _ = w.Close() }() + if err = trace.Start(w); err != nil { return fmt.Errorf("failed to start trace: %w", err) } @@ -115,10 +117,12 @@ func ParseAndGenerateConnector(args ConnectorGenerationArguments, moduleName str typeBuilders: make(map[string]*connectorTypeBuilder), typeOnly: args.TypeOnly, } + connectorPkgName, err := connectorGen.loadConnectorPackage() if err != nil { return err } + return connectorGen.generateConnector(connectorPkgName) } @@ -315,8 +319,15 @@ func (cg *connectorGenerator) writeToMapProperty(sb *connectorTypeBuilder, field switch t := ty.(type) { case *NullableType: sb.builder.WriteString(fmt.Sprintf(" if %s != nil {\n", selector)) - propName := cg.writeToMapProperty(sb, field, fmt.Sprintf("(*%s)", selector), assigner, t.UnderlyingType) + newSelector := selector + + if t.UnderlyingType.Kind() != schema.TypePredicate { + newSelector = fmt.Sprintf("(*%s)", selector) + } + + propName := cg.writeToMapProperty(sb, field, newSelector, assigner, t.UnderlyingType) sb.builder.WriteString(" }\n") + return propName case *ArrayType: varName := formatLocalFieldName(selector) @@ -326,6 +337,7 @@ func (cg *connectorGenerator) writeToMapProperty(sb *connectorTypeBuilder, field cg.writeToMapProperty(sb, field, valueName, varName+"[i]", t.ElementType) sb.builder.WriteString(" }\n") sb.builder.WriteString(fmt.Sprintf(" %s = %s\n", assigner, varName)) + return varName case *NamedType: innerObject, ok := cg.rawSchema.Objects[t.Name] @@ -339,16 +351,23 @@ func (cg *connectorGenerator) writeToMapProperty(sb *connectorTypeBuilder, field sb.builder.WriteString(fmt.Sprintf(" %s := make(map[string]any)\n", varName)) cg.writeObjectToMap(sb, &innerObject, selector, varName) sb.builder.WriteString(fmt.Sprintf(" %s = %s\n", assigner, varName)) + return varName } if !field.Embedded { sb.builder.WriteString(fmt.Sprintf(" %s = %s\n", assigner, selector)) + return selector } sb.imports[packageSDKUtils] = "" sb.builder.WriteString(fmt.Sprintf(" r = utils.MergeMap(r, %s.ToMap())\n", selector)) + + return selector + case *PredicateType: + sb.builder.WriteString(fmt.Sprintf(" %s = %s\n", assigner, selector)) + return selector default: panic(fmt.Errorf("failed to write the ToMap method; invalid type: %s", ty)) @@ -549,6 +568,7 @@ func (cg *connectorGenerator) writeGetTypeValueDecoder(sb *connectorTypeBuilder, fieldName := field.Name typeName := ty.String() fullTypeName := ty.FullName() + if strings.Contains(typeName, "complex64") || strings.Contains(typeName, "complex128") || strings.Contains(typeName, "time.Duration") { panic(fmt.Errorf("unsupported type: %s", typeName)) } @@ -644,68 +664,82 @@ func (cg *connectorGenerator) writeGetTypeValueDecoder(sb *connectorTypeBuilder, cg.writeScalarDecodeValue(sb, fieldName, "GetArbitraryJSONPtrSlice", "", key, objectField, false) case "*[]*any", "*[]*interface{}": cg.writeScalarDecodeValue(sb, fieldName, "GetNullableArbitraryJSONPtrSlice", "", key, objectField, true) + case fmt.Sprintf("*%s.Expression", packageSDKSchema): + cg.writeScalarDecodeValue(sb, fieldName, "GetNullableUUID", "", key, objectField, true) default: sb.imports[packageSDKUtils] = "" sb.builder.WriteString(" j.") sb.builder.WriteString(fieldName) sb.builder.WriteString(", err = utils.") - switch t := ty.(type) { - case *NullableType: - var tyName string - var packagePaths []string - if t.IsAnonymous() { - tyName, packagePaths = cg.getAnonymousObjectTypeName(sb, field.TypeAST, true) - } else { - packagePaths = getTypePackagePaths(t.UnderlyingType, sb.packagePath) - tyName = getTypeArgumentName(t.UnderlyingType, sb.packagePath, false) - } - for _, pkgPath := range packagePaths { - sb.imports[pkgPath] = "" - } - if field.Embedded { - sb.builder.WriteString("DecodeNullableObject[") - sb.builder.WriteString(tyName) - sb.builder.WriteString("](input)") - } else { - sb.builder.WriteString("DecodeNullableObjectValue[") - sb.builder.WriteString(tyName) - sb.builder.WriteString(`](input, "`) - sb.builder.WriteString(key) - sb.builder.WriteString(`")`) - } - default: - var tyName string - var packagePaths []string - if t.IsAnonymous() { - tyName, packagePaths = cg.getAnonymousObjectTypeName(sb, field.TypeAST, true) - } else { - packagePaths = getTypePackagePaths(ty, sb.packagePath) - tyName = getTypeArgumentName(ty, sb.packagePath, false) - } - for _, pkgPath := range packagePaths { - sb.imports[pkgPath] = "" + + t := ty + var tyName string + var packagePaths []string + + if nt, ok := ty.(*NullableType); ok { + if nt.UnderlyingType.Kind() != schema.TypePredicate { + if nt.IsAnonymous() { + tyName, packagePaths = cg.getAnonymousObjectTypeName(sb, field.TypeAST, true) + } else { + packagePaths = getTypePackagePaths(nt.UnderlyingType, sb.packagePath) + tyName = getTypeArgumentName(nt.UnderlyingType, sb.packagePath, false) + } + + for _, pkgPath := range packagePaths { + sb.imports[pkgPath] = "" + } + + if field.Embedded { + sb.builder.WriteString("DecodeNullableObject[") + sb.builder.WriteString(tyName) + sb.builder.WriteString("](input)") + } else { + sb.builder.WriteString("DecodeNullableObjectValue[") + sb.builder.WriteString(tyName) + sb.builder.WriteString(`](input, "`) + sb.builder.WriteString(key) + sb.builder.WriteString(`")`) + } + + break } - if field.Embedded { - sb.builder.WriteString("DecodeObject") - sb.builder.WriteRune('[') - sb.builder.WriteString(tyName) - sb.builder.WriteString("](input)") - } else { - sb.builder.WriteString("DecodeObjectValue") - if len(objectField.Type) > 0 { - if typeEnum, err := objectField.Type.Type(); err == nil && typeEnum == schema.TypeNullable { - sb.builder.WriteString("Default") - } + t = nt.UnderlyingType + } + + if t.IsAnonymous() { + tyName, packagePaths = cg.getAnonymousObjectTypeName(sb, field.TypeAST, true) + } else { + packagePaths = getTypePackagePaths(t, sb.packagePath) + tyName = getTypeArgumentName(t, sb.packagePath, false) + } + + for _, pkgPath := range packagePaths { + sb.imports[pkgPath] = "" + } + + if field.Embedded { + sb.builder.WriteString("DecodeObject") + sb.builder.WriteRune('[') + sb.builder.WriteString(tyName) + sb.builder.WriteString("](input)") + } else { + sb.builder.WriteString("DecodeObjectValue") + + if len(objectField.Type) > 0 { + if typeEnum, err := objectField.Type.Type(); err == nil && typeEnum == schema.TypeNullable { + sb.builder.WriteString("Default") } - sb.builder.WriteRune('[') - sb.builder.WriteString(tyName) - sb.builder.WriteString(`](input, "`) - sb.builder.WriteString(key) - sb.builder.WriteString(`")`) } + + sb.builder.WriteRune('[') + sb.builder.WriteString(tyName) + sb.builder.WriteString(`](input, "`) + sb.builder.WriteString(key) + sb.builder.WriteString(`")`) } } + writeErrorCheck(sb.builder, 1, 2) } diff --git a/cmd/hasura-ndc-go/command/internal/connector_handler.go b/cmd/hasura-ndc-go/command/internal/connector_handler.go index 5d1e50f3..d14eaa12 100644 --- a/cmd/hasura-ndc-go/command/internal/connector_handler.go +++ b/cmd/hasura-ndc-go/command/internal/connector_handler.go @@ -27,7 +27,7 @@ func (chb connectorHandlerBuilder) Render() { bs.imports["log/slog"] = "" bs.imports["slices"] = "" bs.imports["github.com/hasura/ndc-sdk-go/connector"] = "" - bs.imports["github.com/hasura/ndc-sdk-go/schema"] = "" + bs.imports[packageSDKSchema] = "" bs.imports["go.opentelemetry.io/otel/trace"] = "" bs.imports[packageSDKUtils] = "" diff --git a/cmd/hasura-ndc-go/command/internal/constant.go b/cmd/hasura-ndc-go/command/internal/constant.go index 9c81bf85..073735d0 100644 --- a/cmd/hasura-ndc-go/command/internal/constant.go +++ b/cmd/hasura-ndc-go/command/internal/constant.go @@ -154,5 +154,6 @@ var ( ) const ( - packageSDKUtils = "github.com/hasura/ndc-sdk-go/utils" + packageSDKUtils = "github.com/hasura/ndc-sdk-go/utils" + packageSDKSchema = "github.com/hasura/ndc-sdk-go/schema" ) diff --git a/cmd/hasura-ndc-go/command/internal/schema.go b/cmd/hasura-ndc-go/command/internal/schema.go index 4e9abceb..fcb7c7da 100644 --- a/cmd/hasura-ndc-go/command/internal/schema.go +++ b/cmd/hasura-ndc-go/command/internal/schema.go @@ -3,6 +3,8 @@ package internal import ( "fmt" "go/types" + "slices" + "strings" "github.com/hasura/ndc-sdk-go/schema" ) @@ -150,6 +152,41 @@ func (t *NamedType) String() string { return t.NativeType.String() } +// PredicateType the information of a predicate type +type PredicateType struct { + ObjectName string +} + +var _ Type = &PredicateType{} + +func NewPredicateType(name string) *PredicateType { + return &PredicateType{name} +} + +func (t *PredicateType) Kind() schema.TypeEnum { + return schema.TypePredicate +} + +func (t *PredicateType) IsAnonymous() bool { + return false +} + +func (t *PredicateType) Schema() schema.TypeEncoder { + return schema.NewPredicateType(t.ObjectName) +} + +func (t PredicateType) SchemaName(_ bool) string { + return t.String() +} + +func (t PredicateType) FullName() string { + return t.String() +} + +func (t *PredicateType) String() string { + return "Predicate<" + t.ObjectName + ">" +} + // TypeInfo represents the serialization information of a type. type TypeInfo struct { Name string @@ -328,16 +365,27 @@ func (rcs RawConnectorSchema) Schema() *schema.SchemaResponse { for key, item := range rcs.Scalars { result.ScalarTypes[key] = item.Schema } + for _, obj := range rcs.Objects { result.ObjectTypes[obj.Type.SchemaName] = *obj.Schema() } + for _, function := range rcs.Functions { result.Functions = append(result.Functions, function.Schema()) } + + slices.SortFunc(result.Functions, func(a, b schema.FunctionInfo) int { + return strings.Compare(a.Name, b.Name) + }) + for _, procedure := range rcs.Procedures { result.Procedures = append(result.Procedures, procedure.Schema()) } + slices.SortFunc(result.Procedures, func(a, b schema.ProcedureInfo) int { + return strings.Compare(a.Name, b.Name) + }) + return result } @@ -382,6 +430,12 @@ func getTypeArgumentName(input Type, packagePath string, isAbsolute bool) string return "[]" + getTypeArgumentName(t.ElementType, packagePath, isAbsolute) case *NamedType: return t.NativeType.getArgumentName(packagePath, isAbsolute) + case *PredicateType: + if isAbsolute { + return fmt.Sprintf("%s.%s", packageSDKSchema, "Expression") + } + + return "schema.Expression" default: panic(fmt.Errorf("getTypeArgumentName: invalid type %v", input)) } @@ -395,6 +449,8 @@ func getTypePackagePaths(input Type, currentPackagePath string) []string { return getTypePackagePaths(t.ElementType, currentPackagePath) case *NamedType: return t.NativeType.GetPackagePaths(currentPackagePath) + case *PredicateType: + return []string{packageSDKSchema} default: panic(fmt.Errorf("getTypePackagePaths: invalid type %v", input)) } diff --git a/cmd/hasura-ndc-go/command/internal/schema_parser.go b/cmd/hasura-ndc-go/command/internal/schema_parser.go index c430c0cf..0033b13d 100644 --- a/cmd/hasura-ndc-go/command/internal/schema_parser.go +++ b/cmd/hasura-ndc-go/command/internal/schema_parser.go @@ -218,7 +218,7 @@ func (sp *SchemaParser) parsePackageScope(pkg *types.Package, name string) error // ignore 2 first parameters (context and state) if params.Len() == 3 { arg := params.At(2) - argumentParser := NewTypeParser(sp, &Field{}, arg.Type(), &opInfo.Kind) + argumentParser := NewTypeParser(sp, &Field{}, arg.Type(), NDCTagInfo{}, &opInfo.Kind) argumentInfo, err := argumentParser.ParseArgumentTypes([]string{}) if err != nil { return err @@ -236,7 +236,7 @@ func (sp *SchemaParser) parsePackageScope(pkg *types.Package, name string) error } } - typeParser := NewTypeParser(sp, &Field{}, resultTuple.At(0).Type(), nil) + typeParser := NewTypeParser(sp, &Field{}, resultTuple.At(0).Type(), NDCTagInfo{}, nil) resultType, err := typeParser.Parse([]string{}) if err != nil { return err diff --git a/cmd/hasura-ndc-go/command/internal/schema_template.go b/cmd/hasura-ndc-go/command/internal/schema_template.go index d00791a5..8c4efabd 100644 --- a/cmd/hasura-ndc-go/command/internal/schema_template.go +++ b/cmd/hasura-ndc-go/command/internal/schema_template.go @@ -258,6 +258,8 @@ func (rcs RawConnectorSchema) writeType(schemaType schema.Type, depth uint) (str result += fmt.Sprintf("NewNullableType(%s)", nested) case *schema.NamedType: result += fmt.Sprintf(`NewNamedType("%s")`, t.Name) + case *schema.PredicateType: + result += fmt.Sprintf(`NewPredicateType("%s")`, t.ObjectTypeName) default: return "", fmt.Errorf("invalid schema type: %w", err) } diff --git a/cmd/hasura-ndc-go/command/internal/schema_type_parser.go b/cmd/hasura-ndc-go/command/internal/schema_type_parser.go index ad7ce024..aaf8fa55 100644 --- a/cmd/hasura-ndc-go/command/internal/schema_type_parser.go +++ b/cmd/hasura-ndc-go/command/internal/schema_type_parser.go @@ -6,9 +6,7 @@ import ( "go/types" "strings" - "github.com/fatih/structtag" "github.com/hasura/ndc-sdk-go/schema" - "github.com/rs/zerolog/log" ) type TypeParser struct { @@ -16,15 +14,17 @@ type TypeParser struct { field *Field rootType types.Type argumentFor *OperationKind + tagInfo NDCTagInfo // cached parent named type if the underlying type is an object typeInfo *TypeInfo } -func NewTypeParser(schemaParser *SchemaParser, field *Field, ty types.Type, argumentFor *OperationKind) *TypeParser { +func NewTypeParser(schemaParser *SchemaParser, field *Field, ty types.Type, tagInfo NDCTagInfo, argumentFor *OperationKind) *TypeParser { return &TypeParser{ schemaParser: schemaParser, field: field, rootType: ty, + tagInfo: tagInfo, argumentFor: argumentFor, } } @@ -54,18 +54,24 @@ func (tp *TypeParser) parseArgumentTypes(ty types.Type, fieldPaths []string) (*O Fields: map[string]Field{}, SchemaFields: schema.ObjectTypeFields{}, } + if err := tp.parseStructType(result, inferredType, fieldPaths); err != nil { return nil, err } + return result, nil case *types.Named: - typeObj := inferredType.Obj() + if typeObj == nil { + return nil, fmt.Errorf("named type %s does not exist", inferredType.String()) + } + typeInfo := &TypeInfo{ Name: typeObj.Name(), SchemaName: typeObj.Name(), TypeAST: typeObj.Type().Underlying(), } + pkg := typeObj.Pkg() if pkg != nil { typeInfo.PackagePath = pkg.Path() @@ -230,6 +236,14 @@ func (tp *TypeParser) parseType(ty types.Type, fieldPaths []string) (Type, error default: return nil, fmt.Errorf("unsupported type %s.%s", innerPkg.Path(), innerType.Name()) } + case packageSDKSchema: + if innerType.Name() == "Expression" && tp.argumentFor != nil { + if tp.tagInfo.PredicateObjectName == "" { + return nil, fmt.Errorf("%s: predicate field tag must be set `ndc:\"predicate=\"`", strings.Join(fieldPaths, ".")) + } + + return NewNullableType(NewPredicateType(tp.tagInfo.PredicateObjectName)), nil + } case "github.com/hasura/ndc-sdk-go/scalar": switch innerType.Name() { case "Date", "BigInt", "Bytes", "URL", "Duration": @@ -332,19 +346,32 @@ func (tp *TypeParser) parseStructType(objectInfo *ObjectInfo, inferredType *type } fieldTag := inferredType.Tag(i) - fieldKey, jsonOption := getFieldNameOrTag(fieldVar.Name(), fieldTag) - if jsonOption == jsonIgnore { + + tagInfo, err := parseNDCTagInfo(fieldTag) + if err != nil { + return fmt.Errorf("%s: %w", strings.Join(fieldPaths, "."), err) + } + + if tagInfo.Ignored { continue } + + fieldKey := tagInfo.Name + if fieldKey == "" { + fieldKey = fieldVar.Name() + } + fieldParser := NewTypeParser(tp.schemaParser, &Field{ Name: fieldVar.Name(), Embedded: fieldVar.Embedded(), TypeAST: fieldVar.Type(), - }, fieldVar.Type(), tp.argumentFor) + }, fieldVar.Type(), tagInfo, tp.argumentFor) + field, err := fieldParser.Parse(append(fieldPaths, fieldVar.Name())) if err != nil { return err } + if field == nil { continue } @@ -356,9 +383,10 @@ func (tp *TypeParser) parseStructType(objectInfo *ObjectInfo, inferredType *type } } else { fieldSchema := field.Type.Schema() - if jsonOption == jsonOmitEmpty && field.Type.Kind() != schema.TypeNullable { + if tagInfo.OmitEmpty && field.Type.Kind() != schema.TypeNullable { fieldSchema = schema.NewNullableType(fieldSchema) } + objectInfo.SchemaFields[fieldKey] = schema.ObjectField{ Type: fieldSchema.Encode(), } @@ -391,8 +419,7 @@ func (tp *TypeParser) parseTypeInfoFromComments(typeInfo *TypeInfo, scope *types text = strings.TrimPrefix(text, typeInfo.Name+" ") } - enumMatches := ndcEnumCommentRegex.FindStringSubmatch(text) - + enumMatches := ndcEnumCommentRegex.FindStringSubmatch(strings.TrimRight(text, ".")) if len(enumMatches) == 2 { rawEnumItems := strings.Split(enumMatches[1], ",") var enums []string @@ -539,35 +566,3 @@ func parseTypeFromString(input string) (Type, error) { return NewNamedType(typeInfo.Name, typeInfo), nil } - -const ( - jsonOmitEmpty = "omitempty" - jsonIgnore = "-" -) - -// Get field name and options by json tag. -// Return the struct field name if not exist. -func getFieldNameOrTag(name string, tag string) (string, string) { - if tag == "" { - return name, "" - } - tags, err := structtag.Parse(tag) - if err != nil { - log.Warn().Err(err).Msgf("failed to parse tag of struct field: %s", name) - return name, "" - } - - jsonTag, err := tags.Get("json") - if err != nil { - log.Warn().Err(err).Msgf("json tag does not exist in struct field: %s", name) - return name, "" - } - if jsonTag.Value() == "-" { - return name, jsonIgnore - } - nameParts := strings.Split(jsonTag.Value(), ",") - if len(nameParts) == 1 { - return jsonTag.Name, "" - } - return nameParts[0], nameParts[1] -} diff --git a/cmd/hasura-ndc-go/command/internal/tag.go b/cmd/hasura-ndc-go/command/internal/tag.go new file mode 100644 index 00000000..67998be8 --- /dev/null +++ b/cmd/hasura-ndc-go/command/internal/tag.go @@ -0,0 +1,64 @@ +package internal + +import ( + "fmt" + "strings" + + "github.com/fatih/structtag" +) + +// NDCTagInfo holds information of an object field that is parsed from the struct tag. +type NDCTagInfo struct { + Name string + Ignored bool + OmitEmpty bool + PredicateObjectName string +} + +// parses connector and json tag information from the raw struct tag. +func parseNDCTagInfo(input string) (NDCTagInfo, error) { + result := NDCTagInfo{} + if input == "" { + return result, nil + } + + tags, err := structtag.Parse(input) + if err != nil || tags == nil { + return result, err + } + + jsonTag, err := tags.Get("json") + if err == nil { + if jsonTag.Value() == "-" { + result.Ignored = true + } + + result.Name = strings.TrimSpace(jsonTag.Name) + result.OmitEmpty = jsonTag.HasOption("omitempty") + } + + rawTag, err := tags.Get("ndc") + if err != nil { + return result, nil //nolint:nilerr + } + + for _, tag := range append(rawTag.Options, rawTag.Name) { + keyValue := strings.Split(strings.TrimSpace(tag), "=") + if len(keyValue) != 2 { + return result, fmt.Errorf("invalid tag: %s", tag) + } + + if keyValue[1] == "" { + return result, fmt.Errorf("value of %s tag is empty", keyValue[0]) + } + + switch keyValue[0] { + case "predicate": + result.PredicateObjectName = keyValue[1] + default: + return result, fmt.Errorf("invalid tag key: %s", keyValue[0]) + } + } + + return result, nil +} diff --git a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/functions/types.generated.go.tmpl b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/functions/types.generated.go.tmpl index 101f4593..b4d046bb 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/functions/types.generated.go.tmpl +++ b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/functions/types.generated.go.tmpl @@ -826,6 +826,10 @@ func (j *GetTypesArguments) FromValue(input map[string]any) error { if err != nil { return err } + j.Where, err = utils.DecodeObjectValueDefault[schema.Expression](input, "where") + if err != nil { + return err + } return nil } // ToMap encodes the struct to a value map @@ -1103,6 +1107,7 @@ func (j GetTypesArguments) ToMap() map[string]any { r["uint_empty"] = j.UintEmpty r["url_empty"] = j.URLEmpty r["uuid_empty"] = j.UUIDEmpty + r["where"] = j.Where return r } diff --git a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.go.tmpl b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.go.tmpl index 632640e5..7eb7a7f1 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.go.tmpl +++ b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.go.tmpl @@ -696,6 +696,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "uuid_empty": schema.ObjectField{ Type: schema.NewNullableType(schema.NewNamedType("UUID")).Encode(), }, + "where": schema.ObjectField{ + Type: schema.NewNamedType("JSON").Encode(), + }, }, }, "GetTypesArgumentsArrayObject": schema.ObjectType{ @@ -1361,6 +1364,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "uuid_empty": { Type: schema.NewNullableType(schema.NewNamedType("UUID")).Encode(), }, + "where": { + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, { @@ -1407,6 +1413,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": { Type: schema.NewNamedType("String").Encode(), }, + "where": { + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, { diff --git a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.json b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.json index 9b072322..476aca40 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.json +++ b/cmd/hasura-ndc-go/command/internal/testdata/basic/expected/schema.json @@ -1,6 +1,25 @@ { "collections": [], "functions": [ + { + "arguments": { + "Limit": { + "type": { + "name": "Float64", + "type": "named" + } + } + }, + "description": "GetArticles", + "name": "getArticles", + "result_type": { + "element_type": { + "name": "GetArticlesResult", + "type": "named" + }, + "type": "array" + } + }, { "arguments": {}, "description": "return an scalar boolean", @@ -2114,6 +2133,15 @@ "type": "named" } } + }, + "where": { + "type": { + "type": "nullable", + "underlying_type": { + "object_type_name": "Author", + "type": "predicate" + } + } } }, "name": "getTypes", @@ -2136,25 +2164,6 @@ "type": "named" } } - }, - { - "arguments": { - "Limit": { - "type": { - "name": "Float64", - "type": "named" - } - } - }, - "description": "GetArticles", - "name": "getArticles", - "result_type": { - "element_type": { - "name": "GetArticlesResult", - "type": "named" - }, - "type": "array" - } } ], "object_types": { @@ -4398,6 +4407,12 @@ "type": "named" } } + }, + "where": { + "type": { + "name": "JSON", + "type": "named" + } } } }, @@ -4492,34 +4507,6 @@ } }, "procedures": [ - { - "arguments": { - "author": { - "type": { - "name": "CreateArticleArgumentsAuthor", - "type": "named" - } - } - }, - "description": "CreateArticle", - "name": "create_article", - "result_type": { - "type": "nullable", - "underlying_type": { - "name": "CreateArticleResult", - "type": "named" - } - } - }, - { - "arguments": {}, - "description": "Increase", - "name": "increase", - "result_type": { - "name": "Int32", - "type": "named" - } - }, { "arguments": { "name": { @@ -4527,6 +4514,15 @@ "name": "String", "type": "named" } + }, + "where": { + "type": { + "type": "nullable", + "underlying_type": { + "object_type_name": "Author", + "type": "predicate" + } + } } }, "description": "creates an author", @@ -4561,6 +4557,25 @@ "type": "array" } }, + { + "arguments": { + "author": { + "type": { + "name": "CreateArticleArgumentsAuthor", + "type": "named" + } + } + }, + "description": "CreateArticle", + "name": "create_article", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "CreateArticleResult", + "type": "named" + } + } + }, { "arguments": { "headers": { @@ -4599,6 +4614,15 @@ "type": "named" } } + }, + { + "arguments": {}, + "description": "Increase", + "name": "increase", + "result_type": { + "name": "Int32", + "type": "named" + } } ], "scalar_types": { diff --git a/cmd/hasura-ndc-go/command/internal/testdata/basic/source/functions/prefix.go b/cmd/hasura-ndc-go/command/internal/testdata/basic/source/functions/prefix.go index 8b04080b..31ddc5cc 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/basic/source/functions/prefix.go +++ b/cmd/hasura-ndc-go/command/internal/testdata/basic/source/functions/prefix.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/hasura/ndc-codegen-test/types" "github.com/hasura/ndc-sdk-go/scalar" + "github.com/hasura/ndc-sdk-go/schema" ) type Text string @@ -45,7 +46,8 @@ func FunctionHello(ctx context.Context, state *types.State) (*HelloResult, error // A create author argument type CreateAuthorArguments struct { - Name string `json:"name"` + Name string `json:"name"` + Where schema.Expression `json:"where" ndc:"predicate=Author"` } // A create authors argument @@ -297,7 +299,8 @@ type GetTypesArguments struct { ArrayTimeEmpty []time.Time `json:"array_time_empty,omitempty"` ArrayTimePtrEmpty []*time.Time `json:"array_time_ptr_empty,omitempty"` - IgnoredField string `json:"-"` + IgnoredField string `json:"-"` + Where schema.Expression `json:"where" ndc:"predicate=Author"` } func FunctionGetTypes(ctx context.Context, state *types.State, arguments *GetTypesArguments) (*GetTypesArguments, error) { diff --git a/cmd/hasura-ndc-go/command/internal/testdata/single_op/expected/schema.json b/cmd/hasura-ndc-go/command/internal/testdata/single_op/expected/schema.json index e9cc319b..9d74e50a 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/single_op/expected/schema.json +++ b/cmd/hasura-ndc-go/command/internal/testdata/single_op/expected/schema.json @@ -3,17 +3,17 @@ "functions": [ { "arguments": {}, - "name": "simpleObject", + "name": "hello", "result_type": { - "name": "SimpleResult", + "name": "String", "type": "named" } }, { "arguments": {}, - "name": "hello", + "name": "simpleObject", "result_type": { - "name": "String", + "name": "SimpleResult", "type": "named" } } diff --git a/cmd/hasura-ndc-go/command/internal/testdata/snake_case/expected/schema.json b/cmd/hasura-ndc-go/command/internal/testdata/snake_case/expected/schema.json index 99fca05f..f153dd32 100644 --- a/cmd/hasura-ndc-go/command/internal/testdata/snake_case/expected/schema.json +++ b/cmd/hasura-ndc-go/command/internal/testdata/snake_case/expected/schema.json @@ -1,6 +1,25 @@ { "collections": [], "functions": [ + { + "arguments": { + "Limit": { + "type": { + "name": "Float64", + "type": "named" + } + } + }, + "description": "GetArticles", + "name": "get_articles", + "result_type": { + "element_type": { + "name": "GetArticlesResult", + "type": "named" + }, + "type": "array" + } + }, { "arguments": {}, "description": "return an scalar boolean", @@ -1413,25 +1432,6 @@ "type": "named" } } - }, - { - "arguments": { - "Limit": { - "type": { - "name": "Float64", - "type": "named" - } - } - }, - "description": "GetArticles", - "name": "get_articles", - "result_type": { - "element_type": { - "name": "GetArticlesResult", - "type": "named" - }, - "type": "array" - } } ], "object_types": { @@ -3018,15 +3018,6 @@ } } }, - { - "arguments": {}, - "description": "Increase", - "name": "increase", - "result_type": { - "name": "Int32", - "type": "named" - } - }, { "arguments": { "name": { @@ -3067,6 +3058,15 @@ }, "type": "array" } + }, + { + "arguments": {}, + "description": "Increase", + "name": "increase", + "result_type": { + "name": "Int32", + "type": "named" + } } ], "scalar_types": { diff --git a/example/codegen/functions/prefix.go b/example/codegen/functions/prefix.go index a971d821..ae10ee3b 100644 --- a/example/codegen/functions/prefix.go +++ b/example/codegen/functions/prefix.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/hasura/ndc-codegen-example/types" "github.com/hasura/ndc-codegen-example/types/arguments" + "github.com/hasura/ndc-sdk-go/schema" "github.com/hasura/ndc-sdk-go/utils" ) @@ -50,16 +51,20 @@ func FunctionHello(ctx context.Context, state *types.State) (*HelloResult, error // A create author argument type CreateAuthorArguments struct { BaseAuthor + + Where schema.Expression `json:"where" ndc:"predicate=Author"` } + type CreateAuthorsArguments struct { Authors []CreateAuthorArguments } // A create author result type CreateAuthorResult struct { - ID int `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` + ID int `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + Where schema.Expression `json:"where"` } // ProcedureCreateAuthor creates an author @@ -70,8 +75,9 @@ func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *C } return &CreateAuthorResult{ - ID: 1, - Name: arguments.Name, + ID: 1, + Name: arguments.Name, + Where: arguments.Where, }, nil } @@ -113,13 +119,15 @@ type BaseAuthor struct { type GetAuthorArguments struct { *BaseAuthor - ID string `json:"id"` + ID string `json:"id"` + Where schema.Expression `json:"where" ndc:"predicate=Author"` } type GetAuthorResult struct { *CreateAuthorResult - Disabled bool `json:"disabled"` + Where schema.Expression `json:"where"` + Disabled bool `json:"disabled"` } func FunctionGetAuthor(ctx context.Context, state *types.State, arguments *GetAuthorArguments) (*GetAuthorResult, error) { @@ -128,6 +136,7 @@ func FunctionGetAuthor(ctx context.Context, state *types.State, arguments *GetAu ID: 1, Name: arguments.Name, }, + Where: arguments.Where, Disabled: false, }, nil } diff --git a/example/codegen/functions/types.generated.go b/example/codegen/functions/types.generated.go index 19f4e36e..3ccc5382 100644 --- a/example/codegen/functions/types.generated.go +++ b/example/codegen/functions/types.generated.go @@ -53,6 +53,10 @@ func (j *GetAuthorArguments) FromValue(input map[string]any) error { if err != nil { return err } + j.Where, err = utils.DecodeObjectValueDefault[schema.Expression](input, "where") + if err != nil { + return err + } return nil } @@ -81,6 +85,9 @@ func (j CreateArticleResult) ToMap() map[string]any { func (j CreateAuthorArguments) ToMap() map[string]any { r := make(map[string]any) r = utils.MergeMap(r, j.BaseAuthor.ToMap()) + if j.Where != nil { + r["where"] = j.Where + } return r } @@ -91,6 +98,7 @@ func (j CreateAuthorResult) ToMap() map[string]any { r["created_at"] = j.CreatedAt r["id"] = j.ID r["name"] = j.Name + r["where"] = j.Where return r } @@ -111,6 +119,7 @@ func (j GetAuthorResult) ToMap() map[string]any { r = utils.MergeMap(r, (*j.CreateAuthorResult).ToMap()) } r["disabled"] = j.Disabled + r["where"] = j.Where return r } diff --git a/example/codegen/go.mod b/example/codegen/go.mod index 51d973a7..8dfee954 100644 --- a/example/codegen/go.mod +++ b/example/codegen/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 - github.com/hasura/ndc-sdk-go v1.6.4 + github.com/hasura/ndc-sdk-go v1.7.0 go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel/trace v1.29.0 golang.org/x/sync v0.10.0 diff --git a/example/codegen/schema.generated.go b/example/codegen/schema.generated.go index a010f79f..8ebc0a51 100644 --- a/example/codegen/schema.generated.go +++ b/example/codegen/schema.generated.go @@ -65,6 +65,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": schema.ObjectField{ Type: schema.NewNamedType("String").Encode(), }, + "where": schema.ObjectField{ + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, "CreateAuthorResult": schema.ObjectType{ @@ -78,6 +81,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": schema.ObjectField{ Type: schema.NewNamedType("String").Encode(), }, + "where": schema.ObjectField{ + Type: schema.NewNamedType("JSON").Encode(), + }, }, }, "CustomHeadersResult_Author": schema.ObjectType{ @@ -124,6 +130,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": schema.ObjectField{ Type: schema.NewNamedType("String").Encode(), }, + "where": schema.ObjectField{ + Type: schema.NewNamedType("JSON").Encode(), + }, }, }, "GetCustomHeadersInput": schema.ObjectType{ @@ -841,6 +850,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": { Type: schema.NewNamedType("String").Encode(), }, + "where": { + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, { @@ -853,6 +865,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": { Type: schema.NewNamedType("String").Encode(), }, + "where": { + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, { @@ -1557,6 +1572,9 @@ func GetConnectorSchema() *schema.SchemaResponse { "name": { Type: schema.NewNamedType("String").Encode(), }, + "where": { + Type: schema.NewNullableType(schema.NewPredicateType("Author")).Encode(), + }, }, }, { diff --git a/example/codegen/testdata/mutation/createAuthor/expected.json b/example/codegen/testdata/mutation/createAuthor/expected.json index dee22369..b2275b7b 100644 --- a/example/codegen/testdata/mutation/createAuthor/expected.json +++ b/example/codegen/testdata/mutation/createAuthor/expected.json @@ -4,7 +4,36 @@ "result": { "created_at": "0001-01-01T00:00:00Z", "id": 1, - "name": "q2Ia7pg5VL" + "name": "q2Ia7pg5VL", + "where": { + "expressions": [ + { + "column": { + "name": "name", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "hello" + } + }, + { + "column": { + "name": "id", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "1" + } + } + ], + "type": "and" + } }, "type": "procedure" } diff --git a/example/codegen/testdata/mutation/createAuthor/request.json b/example/codegen/testdata/mutation/createAuthor/request.json index eada29c0..54a012bb 100644 --- a/example/codegen/testdata/mutation/createAuthor/request.json +++ b/example/codegen/testdata/mutation/createAuthor/request.json @@ -5,7 +5,36 @@ "type": "procedure", "name": "createAuthor", "arguments": { - "name": "q2Ia7pg5VL" + "name": "q2Ia7pg5VL", + "where": { + "expressions": [ + { + "column": { + "name": "name", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "hello" + } + }, + { + "column": { + "name": "id", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "1" + } + } + ], + "type": "and" + } }, "fields": { "fields": { @@ -20,10 +49,14 @@ "name": { "column": "name", "type": "column" + }, + "where": { + "column": "where", + "type": "column" } }, "type": "object" } } ] -} \ No newline at end of file +} diff --git a/example/codegen/testdata/query/getAuthor/expected.json b/example/codegen/testdata/query/getAuthor/expected.json index 7b4a5b25..f601983d 100644 --- a/example/codegen/testdata/query/getAuthor/expected.json +++ b/example/codegen/testdata/query/getAuthor/expected.json @@ -4,7 +4,36 @@ { "__value": { "id": 1, - "name": "3uPAoDW2BB" + "name": "3uPAoDW2BB", + "where": { + "expressions": [ + { + "column": { + "name": "name", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "hello" + } + }, + { + "column": { + "name": "id", + "type": "column" + }, + "operator": "_eq", + "type": "binary_comparison_operator", + "value": { + "type": "scalar", + "value": "1" + } + } + ], + "type": "and" + } } } ] diff --git a/example/codegen/testdata/query/getAuthor/request.json b/example/codegen/testdata/query/getAuthor/request.json index ffa91ef7..52c9ca40 100644 --- a/example/codegen/testdata/query/getAuthor/request.json +++ b/example/codegen/testdata/query/getAuthor/request.json @@ -7,6 +7,26 @@ "name": { "type": "literal", "value": "3uPAoDW2BB" + }, + "where": { + "type": "literal", + "value": { + "type": "and", + "expressions": [ + { + "type": "binary_comparison_operator", + "column": { "type": "column", "name": "name", "path": [] }, + "operator": "_eq", + "value": { "type": "scalar", "value": "hello" } + }, + { + "type": "binary_comparison_operator", + "column": { "type": "column", "name": "id", "path": [] }, + "operator": "_eq", + "value": { "type": "scalar", "value": "1" } + } + ] + } } }, "collection": "getAuthor", @@ -24,6 +44,10 @@ "name": { "column": "name", "type": "column" + }, + "where": { + "column": "where", + "type": "column" } }, "type": "object" @@ -32,4 +56,4 @@ } } } -} \ No newline at end of file +} diff --git a/example/reference/connector.go b/example/reference/connector.go index 6d9e1069..c838d8ba 100644 --- a/example/reference/connector.go +++ b/example/reference/connector.go @@ -1338,7 +1338,12 @@ func evalExpression( root map[string]any, item map[string]any, ) (bool, error) { - switch expression := expr.Interface().(type) { + exprT, err := expr.InterfaceT() + if err != nil { + return false, err + } + + switch expression := exprT.(type) { case *schema.ExpressionAnd: for _, exp := range expression.Expressions { ok, err := evalExpression(collectionRelationships, variables, state, exp, root, item) diff --git a/schema/extend.go b/schema/extend.go index 7f9ba535..343b1b13 100644 --- a/schema/extend.go +++ b/schema/extend.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "slices" + + "github.com/go-viper/mapstructure/v2" ) var errTypeRequired = errors.New("type field is required") @@ -87,7 +89,11 @@ func (j *Argument) UnmarshalJSON(b []byte) error { return err } - rawArgumentType := getStringValueByKey(raw, "type") + rawArgumentType, err := getStringValueByKey(raw, "type") + if err != nil { + return fmt.Errorf("type in Argument: %w", err) + } + if rawArgumentType == "" { return errors.New("field type in Argument: required") } @@ -109,10 +115,15 @@ func (j *Argument) UnmarshalJSON(b []byte) error { arg.Value = value } case ArgumentTypeVariable: - name := getStringValueByKey(raw, "name") + name, err := getStringValueByKey(raw, "name") + if err != nil { + return fmt.Errorf("field name in Argument: %w", err) + } + if name == "" { return errors.New("field name in Argument is required for variable type") } + arg.Name = name } @@ -270,7 +281,11 @@ func (j *RelationshipArgument) UnmarshalJSON(b []byte) error { return err } - rawArgumentType := getStringValueByKey(raw, "type") + rawArgumentType, err := getStringValueByKey(raw, "type") + if err != nil { + return fmt.Errorf("field type in Argument: %w", err) + } + if rawArgumentType == "" { return errors.New("field type in Argument: required") } @@ -292,14 +307,20 @@ func (j *RelationshipArgument) UnmarshalJSON(b []byte) error { arg.Value = value } default: - name := getStringValueByKey(raw, "name") + name, err := getStringValueByKey(raw, "name") + if err != nil { + return fmt.Errorf("field name in Argument: %w", err) + } + if name == "" { return fmt.Errorf("field name in Argument is required for %s type", rawArgumentType) } + arg.Name = name } *j = arg + return nil } @@ -579,10 +600,16 @@ func (j Field) AsColumn() (*ColumnField, error) { if err != nil { return nil, err } + if t != FieldTypeColumn { return nil, fmt.Errorf("invalid Field type; expected %s, got %s", FieldTypeColumn, t) } - column := getStringValueByKey(j, "column") + + column, err := getStringValueByKey(j, "column") + if err != nil { + return nil, fmt.Errorf("ColumnField.column: %w", err) + } + if column == "" { return nil, errors.New("ColumnField.column is required") } @@ -591,12 +618,14 @@ func (j Field) AsColumn() (*ColumnField, error) { Type: t, Column: column, } + rawFields, ok := j["fields"] if ok && !isNil(rawFields) { fields, ok := rawFields.(NestedField) if !ok { return nil, fmt.Errorf("invalid ColumnField.fields type; expected NestedField, got %+v", rawFields) } + result.Fields = fields } @@ -606,6 +635,7 @@ func (j Field) AsColumn() (*ColumnField, error) { if !ok { return nil, fmt.Errorf("invalid ColumnField.arguments type; expected map[string]Argument, got %+v", rawArguments) } + result.Arguments = arguments } @@ -621,7 +651,11 @@ func (j Field) AsRelationship() (*RelationshipField, error) { if t != FieldTypeRelationship { return nil, fmt.Errorf("invalid Field type; expected %s, got %s", FieldTypeRelationship, t) } - relationship := getStringValueByKey(j, "relationship") + relationship, err := getStringValueByKey(j, "relationship") + if err != nil { + return nil, fmt.Errorf("RelationshipField.relationship: %w", err) + } + if relationship == "" { return nil, errors.New("RelationshipField.relationship is required") } @@ -929,57 +963,65 @@ type ComparisonValue map[string]any // UnmarshalJSON implements json.Unmarshaler. func (j *ComparisonValue) UnmarshalJSON(b []byte) error { - var raw map[string]json.RawMessage + var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } - rawType, ok := raw["type"] - if !ok { - return errors.New("field type in ComparisonValue: required") + return j.FromValue(raw) +} + +// FromValue decodes values from any map. +func (j *ComparisonValue) FromValue(input map[string]any) error { + rawType, err := getStringValueByKey(input, "type") + if err != nil { + return fmt.Errorf("field type in ComparisonValue: %w", err) } - var ty ComparisonValueType - if err := json.Unmarshal(rawType, &ty); err != nil { + ty, err := ParseComparisonValueType(rawType) + if err != nil { return fmt.Errorf("field type in ComparisonValue: %w", err) } result := map[string]any{ "type": ty, } + switch ty { case ComparisonValueTypeVariable: - rawName, ok := raw["name"] - if !ok { - return errors.New("field name in ComparisonValue is required for variable type") - } - var name string - if err := json.Unmarshal(rawName, &name); err != nil { + name, err := getStringValueByKey(input, "name") + if err != nil { return fmt.Errorf("field name in ComparisonValue: %w", err) } + + if name == "" { + return errors.New("field name in ComparisonValue is required for variable type") + } + result["name"] = name case ComparisonValueTypeColumn: - rawColumn, ok := raw["column"] + rawColumn, ok := input["column"] if !ok { return errors.New("field column in ComparisonValue is required for column type") } + var column ComparisonTarget - if err := json.Unmarshal(rawColumn, &column); err != nil { + if err := mapstructure.Decode(rawColumn, &column); err != nil { return fmt.Errorf("field column in ComparisonValue: %w", err) } + result["column"] = column case ComparisonValueTypeScalar: - rawValue, ok := raw["value"] + value, ok := input["value"] if !ok { return errors.New("field value in ComparisonValue is required for scalar type") } - var value any - if err := json.Unmarshal(rawValue, &value); err != nil { - return fmt.Errorf("field value in ComparisonValue: %w", err) - } + result["value"] = value } + *j = result + return nil } @@ -1059,10 +1101,15 @@ func (cv ComparisonValue) AsVariable() (*ComparisonValueVariable, error) { return nil, fmt.Errorf("invalid ComparisonValue type; expected %s, got %s", ComparisonValueTypeVariable, ty) } - name := getStringValueByKey(cv, "name") + name, err := getStringValueByKey(cv, "name") + if err != nil { + return nil, fmt.Errorf("ComparisonValueVariable.name: %w", err) + } + if name == "" { return nil, errors.New("ComparisonValueVariable.name is required") } + return &ComparisonValueVariable{ Type: ty, Name: name, @@ -1216,18 +1263,23 @@ type ExistsInCollection map[string]any // UnmarshalJSON implements json.Unmarshaler. func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { - var raw map[string]json.RawMessage + var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } - rawType, ok := raw["type"] - if !ok { - return errors.New("field type in ExistsInCollection: required") + return j.FromValue(raw) +} + +// FromValue decodes values from any map +func (j *ExistsInCollection) FromValue(input map[string]any) error { + rawType, err := getStringValueByKey(input, "type") + if err != nil { + return fmt.Errorf("field type in ExistsInCollection: %w", err) } - var ty ExistsInCollectionType - if err := json.Unmarshal(rawType, &ty); err != nil { + ty, err := ParseExistsInCollectionType(rawType) + if err != nil { return fmt.Errorf("field type in ExistsInCollection: %w", err) } @@ -1235,12 +1287,13 @@ func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { "type": ty, } - rawArguments, ok := raw["arguments"] + rawArguments, ok := input["arguments"] if ok { var arguments map[string]RelationshipArgument - if err := json.Unmarshal(rawArguments, &arguments); err != nil { + if err := mapstructure.Decode(rawArguments, &arguments); err != nil { return fmt.Errorf("field arguments in ExistsInCollection: %w", err) } + result["arguments"] = arguments } else if ty != ExistsInCollectionTypeNestedCollection { return fmt.Errorf("field arguments in ExistsInCollection is required for %s type", ty) @@ -1248,47 +1301,53 @@ func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { switch ty { case ExistsInCollectionTypeRelated: - rawRelationship, ok := raw["relationship"] - if !ok { - return errors.New("field relationship in ExistsInCollection is required for related type") - } - var relationship string - if err := json.Unmarshal(rawRelationship, &relationship); err != nil { + relationship, err := getStringValueByKey(input, "relationship") + if err != nil { return fmt.Errorf("field name in ExistsInCollection: %w", err) } + + if relationship == "" { + return errors.New("field relationship in ExistsInCollection is required for related type") + } + result["relationship"] = relationship case ExistsInCollectionTypeUnrelated: - rawCollection, ok := raw["collection"] - if !ok { - return errors.New("field collection in ExistsInCollection is required for unrelated type") - } - var collection string - if err := json.Unmarshal(rawCollection, &collection); err != nil { + collection, err := getStringValueByKey(input, "collection") + if err != nil { return fmt.Errorf("field collection in ExistsInCollection: %w", err) } + + if collection == "" { + return errors.New("field collection in ExistsInCollection is required for unrelated type") + } + result["collection"] = collection case ExistsInCollectionTypeNestedCollection: + columnName, err := getStringValueByKey(input, "column_name") + if err != nil { + return fmt.Errorf("field column_name in ExistsInCollection: %w", err) + } - rawColumnName, ok := raw["column_name"] - if !ok { + if columnName == "" { return errors.New("field column_name in ExistsInCollection is required for nested_collection type") } - var columnName string - if err := json.Unmarshal(rawColumnName, &columnName); err != nil { - return fmt.Errorf("field column_name in ExistsInCollection: %w", err) - } + result["column_name"] = columnName - rawFieldPath, ok := raw["field_path"] + rawFieldPath, ok := input["field_path"] if ok { var fieldPath []string - if err := json.Unmarshal(rawFieldPath, &fieldPath); err != nil { + + if err := mapstructure.Decode(rawFieldPath, &fieldPath); err != nil { return fmt.Errorf("field field_path in ExistsInCollection: %w", err) } + result["field_path"] = fieldPath } } + *j = result + return nil } @@ -1322,14 +1381,20 @@ func (j ExistsInCollection) AsRelated() (*ExistsInCollectionRelated, error) { return nil, fmt.Errorf("invalid ExistsInCollection type; expected: %s, got: %s", ExistsInCollectionTypeRelated, t) } - relationship := getStringValueByKey(j, "relationship") + relationship, err := getStringValueByKey(j, "relationship") + if err != nil { + return nil, fmt.Errorf("ExistsInCollectionRelated.relationship: %w", err) + } + if relationship == "" { return nil, errors.New("ExistsInCollectionRelated.relationship is required") } + rawArgs, ok := j["arguments"] if !ok { return nil, errors.New("ExistsInCollectionRelated.arguments is required") } + args, ok := rawArgs.(map[string]RelationshipArgument) if !ok { return nil, fmt.Errorf("invalid ExistsInCollectionRelated.arguments type; expected: map[string]RelationshipArgument, got: %+v", rawArgs) @@ -1352,7 +1417,11 @@ func (j ExistsInCollection) AsUnrelated() (*ExistsInCollectionUnrelated, error) return nil, fmt.Errorf("invalid ExistsInCollection type; expected: %s, got: %s", ExistsInCollectionTypeUnrelated, t) } - collection := getStringValueByKey(j, "collection") + collection, err := getStringValueByKey(j, "collection") + if err != nil { + return nil, fmt.Errorf("ExistsInCollectionUnrelated.collection: %w", err) + } + if collection == "" { return nil, errors.New("ExistsInCollectionUnrelated.collection is required") } @@ -1382,10 +1451,15 @@ func (j ExistsInCollection) AsNestedCollection() (*ExistsInCollectionNestedColle return nil, fmt.Errorf("invalid ExistsInCollection type; expected: %s, got: %s", ExistsInCollectionTypeNestedCollection, t) } - columnName := getStringValueByKey(j, "column_name") + columnName, err := getStringValueByKey(j, "column_name") + if err != nil { + return nil, fmt.Errorf("ExistsInCollectionNestedCollection.column_name: %w", err) + } + if columnName == "" { return nil, errors.New("ExistsInCollectionNestedCollection.column_name is required") } + var args map[string]RelationshipArgument rawArgs, ok := j["arguments"] if ok && rawArgs != nil { @@ -1394,17 +1468,20 @@ func (j ExistsInCollection) AsNestedCollection() (*ExistsInCollectionNestedColle return nil, fmt.Errorf("invalid ExistsInCollectionNestedCollection.arguments type; expected: map[string]RelationshipArgument, got: %+v", rawArgs) } } + result := &ExistsInCollectionNestedCollection{ Type: t, ColumnName: columnName, Arguments: args, } + rawFieldPath, ok := j["field_path"] if ok && rawFieldPath != nil { fieldPath, ok := rawFieldPath.([]string) if !ok { return nil, fmt.Errorf("invalid ExistsInCollectionNestedCollection.fieldPath type; expected: []string, got: %+v", rawArgs) } + result.FieldPath = fieldPath } @@ -1541,120 +1618,163 @@ type Expression map[string]any // UnmarshalJSON implements json.Unmarshaler. func (j *Expression) UnmarshalJSON(b []byte) error { - var raw map[string]json.RawMessage + var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } - rawType, ok := raw["type"] - if !ok { - return errors.New("field type in Expression: required") + return j.FromValue(raw) +} + +// FromValue decodes values from any map +func (j *Expression) FromValue(input map[string]any) error { + rawType, err := getStringValueByKey(input, "type") + if err != nil { + return fmt.Errorf("field type in Expression: %w", err) } - var ty ExpressionType - if err := json.Unmarshal(rawType, &ty); err != nil { + ty, err := ParseExpressionType(rawType) + if err != nil { return fmt.Errorf("field type in Expression: %w", err) } result := map[string]any{ "type": ty, } + switch ty { case ExpressionTypeAnd, ExpressionTypeOr: - rawExpressions, ok := raw["expressions"] - if !ok { + rawExpressions, ok := input["expressions"] + if !ok || rawExpressions == nil { return fmt.Errorf("field expressions in Expression is required for '%s' type", ty) } - var expressions []Expression - if err := json.Unmarshal(rawExpressions, &expressions); err != nil { - return fmt.Errorf("field expressions in Expression: %w", err) + + rawExpressionsArray, ok := rawExpressions.([]any) + if !ok { + return fmt.Errorf("field expressions in Expression: expected array, got %v", rawExpressions) } + + expressions := []Expression{} + for i, rawItem := range rawExpressionsArray { + if rawItem == nil { + continue + } + + itemMap, ok := rawItem.(map[string]any) + if !ok { + return fmt.Errorf("field expressions[%d] in Expression: expected array, got %v", i, rawExpressions) + } + + if itemMap == nil { + continue + } + + expr := Expression{} + if err := expr.FromValue(itemMap); err != nil { + return fmt.Errorf("field expressions in Expression: %w", err) + } + + expressions = append(expressions, expr) + } + result["expressions"] = expressions case ExpressionTypeNot: - rawExpression, ok := raw["expression"] + rawExpression, ok := input["expression"] + if !ok || rawExpression == nil { + return fmt.Errorf("field expressions in Expression is required for '%s' type", ty) + } + + exprMap, ok := rawExpression.(map[string]any) if !ok { return fmt.Errorf("field expressions in Expression is required for '%s' type", ty) } + var expression Expression - if err := json.Unmarshal(rawExpression, &expression); err != nil { + if err := expression.FromValue(exprMap); err != nil { return fmt.Errorf("field expression in Expression: %w", err) } + result["expression"] = expression - case ExpressionTypeUnaryComparisonOperator: - rawOperator, ok := raw["operator"] - if !ok { - return fmt.Errorf("field operator in Expression is required for '%s' type", ty) - } - var operator UnaryComparisonOperator - if err := json.Unmarshal(rawOperator, &operator); err != nil { + case ExpressionTypeUnaryComparisonOperator, ExpressionTypeBinaryComparisonOperator: + rawOperator, err := getStringValueByKey(input, "operator") + if err != nil { return fmt.Errorf("field operator in Expression: %w", err) } - result["operator"] = operator - rawColumn, ok := raw["column"] - if !ok { - return fmt.Errorf("field column in Expression is required for '%s' type", ty) - } - var column ComparisonTarget - if err := json.Unmarshal(rawColumn, &column); err != nil { - return fmt.Errorf("field column in Expression: %w", err) - } - result["column"] = column - case ExpressionTypeBinaryComparisonOperator: - rawOperator, ok := raw["operator"] - if !ok { + if rawOperator == "" { return fmt.Errorf("field operator in Expression is required for '%s' type", ty) } - var operator string - if err := json.Unmarshal(rawOperator, &operator); err != nil { - return fmt.Errorf("field operator in Expression: %w", err) - } - if operator == "" { - return fmt.Errorf("field operator in Expression is required for '%s' type", ty) - } - result["operator"] = operator + result["operator"] = rawOperator - rawColumn, ok := raw["column"] + rawColumn, ok := input["column"] if !ok { return fmt.Errorf("field column in Expression is required for '%s' type", ty) } + var column ComparisonTarget - if err := json.Unmarshal(rawColumn, &column); err != nil { + if err := mapstructure.Decode(rawColumn, &column); err != nil { return fmt.Errorf("field column in Expression: %w", err) } + result["column"] = column - rawValue, ok := raw["value"] - if !ok { + if ty != ExpressionTypeBinaryComparisonOperator { + break + } + + rawValue, ok := input["value"] + if !ok || rawValue == nil { return fmt.Errorf("field value in Expression is required for '%s' type", ty) } + + rawValueMap, ok := rawValue.(map[string]any) + if !ok { + return fmt.Errorf("field value in Expression: expected map, got %v", rawValue) + } + var value ComparisonValue - if err := json.Unmarshal(rawValue, &value); err != nil { + if err := value.FromValue(rawValueMap); err != nil { return fmt.Errorf("field value in Expression: %w", err) } + result["value"] = value case ExpressionTypeExists: - rawPredicate, ok := raw["predicate"] - if ok { - var predicate Expression - if err := json.Unmarshal(rawPredicate, &predicate); err != nil { + rawPredicate, ok := input["predicate"] + if ok && rawPredicate != nil { + rawPredicateMap, ok := rawPredicate.(map[string]any) + if !ok { + return fmt.Errorf("field predicate in Expression: expected map, got %v", rawPredicate) + } + + predicate := Expression{} + if err := predicate.FromValue(rawPredicateMap); err != nil { return fmt.Errorf("field predicate in Expression: %w", err) } + result["predicate"] = predicate } - rawInCollection, ok := raw["in_collection"] - if !ok { + rawInCollection, ok := input["in_collection"] + if !ok || rawInCollection == nil { return fmt.Errorf("field in_collection in Expression is required for '%s' type", ty) } + + rawInCollectionMap, ok := rawInCollection.(map[string]any) + if !ok || rawInCollectionMap == nil { + return fmt.Errorf("field in_collection in Expression is required for '%s' type", ty) + } + var inCollection ExistsInCollection - if err := json.Unmarshal(rawInCollection, &inCollection); err != nil { + if err := inCollection.FromValue(rawInCollectionMap); err != nil { return fmt.Errorf("field in_collection in Expression: %w", err) } + result["in_collection"] = inCollection } + *j = result + return nil } @@ -1694,7 +1814,9 @@ func (j Expression) AsAnd() (*ExpressionAnd, error) { } expressions, ok := rawExpressions.([]Expression) if !ok { - return nil, fmt.Errorf("invalid ExpressionAnd.expression type; expected: []Expression, got: %+v", rawExpressions) + if err := mapstructure.Decode(rawExpressions, &expressions); err != nil { + return nil, fmt.Errorf("invalid ExpressionAnd.expression type; expected: []Expression, got: %v", rawExpressions) + } } return &ExpressionAnd{ @@ -1709,6 +1831,7 @@ func (j Expression) AsOr() (*ExpressionOr, error) { if err != nil { return nil, err } + if t != ExpressionTypeOr { return nil, fmt.Errorf("invalid Expression type; expected: %s, got: %s", ExpressionTypeOr, t) } @@ -1717,9 +1840,12 @@ func (j Expression) AsOr() (*ExpressionOr, error) { if !ok { return nil, errors.New("ExpressionOr.expression is required") } + expressions, ok := rawExpressions.([]Expression) if !ok { - return nil, fmt.Errorf("invalid ExpressionOr.expression type; expected: []Expression, got: %+v", rawExpressions) + if err := mapstructure.Decode(rawExpressions, &expressions); err != nil { + return nil, fmt.Errorf("invalid ExpressionOr.expression type; expected: []Expression, got: %v", rawExpressions) + } } return &ExpressionOr{ @@ -1742,9 +1868,12 @@ func (j Expression) AsNot() (*ExpressionNot, error) { if !ok { return nil, errors.New("ExpressionNot.expression is required") } + expression, ok := rawExpression.(Expression) if !ok { - return nil, fmt.Errorf("invalid ExpressionNot.expression type; expected: Expression, got: %+v", rawExpression) + if err := mapstructure.Decode(rawExpression, &expression); err != nil { + return nil, fmt.Errorf("invalid ExpressionNot.expression type; expected: Expression, got: %+v", rawExpression) + } } return &ExpressionNot{ @@ -1759,6 +1888,7 @@ func (j Expression) AsUnaryComparisonOperator() (*ExpressionUnaryComparisonOpera if err != nil { return nil, err } + if t != ExpressionTypeUnaryComparisonOperator { return nil, fmt.Errorf("invalid Expression type; expected: %s, got: %s", ExpressionTypeUnaryComparisonOperator, t) } @@ -1767,6 +1897,7 @@ func (j Expression) AsUnaryComparisonOperator() (*ExpressionUnaryComparisonOpera if !ok { return nil, errors.New("ExpressionUnaryComparisonOperator.operator is required") } + operator, ok := rawOperator.(UnaryComparisonOperator) if !ok { operatorStr, ok := rawOperator.(string) @@ -1781,6 +1912,7 @@ func (j Expression) AsUnaryComparisonOperator() (*ExpressionUnaryComparisonOpera if !ok { return nil, errors.New("ExpressionUnaryComparisonOperator.column is required") } + column, ok := rawColumn.(ComparisonTarget) if !ok { return nil, fmt.Errorf("invalid ExpressionUnaryComparisonOperator.column type; expected: ComparisonTarget, got: %v", rawColumn) @@ -1821,9 +1953,14 @@ func (j Expression) AsBinaryComparisonOperator() (*ExpressionBinaryComparisonOpe return nil, fmt.Errorf("invalid ExpressionBinaryComparisonOperator.value type; expected: ComparisonValue, got: %+v", rawValue) } + operator, err := getStringValueByKey(j, "operator") + if err != nil { + return nil, fmt.Errorf("invalid ExpressionBinaryComparisonOperator.opeartor: %w", err) + } + return &ExpressionBinaryComparisonOperator{ Type: t, - Operator: getStringValueByKey(j, "operator"), + Operator: operator, Column: column, Value: value, }, nil @@ -1875,6 +2012,7 @@ func (j Expression) InterfaceT() (ExpressionEncoder, error) { if err != nil { return nil, err } + switch t { case ExpressionTypeAnd: return j.AsAnd() @@ -2277,19 +2415,29 @@ func (j Aggregate) AsSingleColumn() (*AggregateSingleColumn, error) { return nil, fmt.Errorf("invalid Aggregate type; expected: %s, got: %s", AggregateTypeSingleColumn, t) } - column := getStringValueByKey(j, "column") + column, err := getStringValueByKey(j, "column") + if err != nil { + return nil, fmt.Errorf("AggregateSingleColumn.column: %w", err) + } + if column == "" { return nil, errors.New("AggregateSingleColumn.column is required") } - function := getStringValueByKey(j, "function") + function, err := getStringValueByKey(j, "function") + if err != nil { + return nil, fmt.Errorf("AggregateSingleColumn.function: %w", err) + } + if function == "" { return nil, errors.New("AggregateSingleColumn.function is required") } + fieldPath, err := j.getFieldPath() if err != nil { return nil, err } + return &AggregateSingleColumn{ Type: t, Column: column, @@ -2308,7 +2456,11 @@ func (j Aggregate) AsColumnCount() (*AggregateColumnCount, error) { return nil, fmt.Errorf("invalid Aggregate type; expected: %s, got: %s", AggregateTypeColumnCount, t) } - column := getStringValueByKey(j, "column") + column, err := getStringValueByKey(j, "column") + if err != nil { + return nil, fmt.Errorf("Aggregate.column: %w", err) + } + if column == "" { return nil, errors.New("AggregateColumnCount.column is required") } @@ -2637,22 +2789,30 @@ func (j OrderByTarget) AsColumn() (*OrderByColumn, error) { if err != nil { return nil, err } + if t != OrderByTargetTypeColumn { return nil, fmt.Errorf("invalid OrderByTarget type; expected: %s, got: %s", OrderByTargetTypeColumn, t) } - name := getStringValueByKey(j, "name") + name, err := getStringValueByKey(j, "name") + if err != nil { + return nil, fmt.Errorf("OrderByColumn.name: %w", err) + } + if name == "" { return nil, errors.New("OrderByColumn.name is required") } + p, err := j.getPath() if err != nil { return nil, err } + fieldPath, err := j.getFieldPath() if err != nil { return nil, err } + return &OrderByColumn{ Type: t, Name: name, @@ -2667,19 +2827,29 @@ func (j OrderByTarget) AsSingleColumnAggregate() (*OrderBySingleColumnAggregate, if err != nil { return nil, err } + if t != OrderByTargetTypeSingleColumnAggregate { return nil, fmt.Errorf("invalid OrderByTarget type; expected: %s, got: %s", OrderByTargetTypeSingleColumnAggregate, t) } - column := getStringValueByKey(j, "column") + column, err := getStringValueByKey(j, "column") + if err != nil { + return nil, fmt.Errorf("OrderBySingleColumnAggregate.column: %w", err) + } + if column == "" { return nil, errors.New("OrderBySingleColumnAggregate.column is required") } - function := getStringValueByKey(j, "function") + function, err := getStringValueByKey(j, "function") + if function == "" { + return nil, fmt.Errorf("OrderBySingleColumnAggregate.function: %w", err) + } + if function == "" { return nil, errors.New("OrderBySingleColumnAggregate.function is required") } + p, err := j.getPath() if err != nil { return nil, err diff --git a/schema/type.go b/schema/type.go index 0dbc0dbf..e3195d53 100644 --- a/schema/type.go +++ b/schema/type.go @@ -169,12 +169,19 @@ func (ty Type) AsNamed() (*NamedType, error) { if err != nil { return nil, err } + if t != TypeNamed { return nil, fmt.Errorf("invalid Type type; expected %s, got %s", TypeNamed, t) } + + name, err := getStringValueByKey(ty, "name") + if err != nil { + return nil, fmt.Errorf("name in NamedType: %w", err) + } + return &NamedType{ Type: t, - Name: getStringValueByKey(ty, "name"), + Name: name, }, nil } @@ -236,9 +243,14 @@ func (ty Type) AsPredicate() (*PredicateType, error) { return nil, fmt.Errorf("invalid Type type; expected %s, got %s", TypePredicate, t) } + name, err := getStringValueByKey(ty, "object_type_name") + if err != nil { + return nil, fmt.Errorf("object_type_name in PredicateType: %w", err) + } + return &PredicateType{ Type: t, - ObjectTypeName: getStringValueByKey(ty, "object_type_name"), + ObjectTypeName: name, }, nil } diff --git a/schema/utils.go b/schema/utils.go index 02299d78..a270a2e7 100644 --- a/schema/utils.go +++ b/schema/utils.go @@ -3,6 +3,7 @@ package schema import ( "encoding/json" "errors" + "fmt" "reflect" ) @@ -19,21 +20,29 @@ func isNullJSON(value []byte) bool { return len(value) == 0 || string(value) == "null" } -func getStringValueByKey(collection map[string]any, key string) string { +func getStringValueByKey(collection map[string]any, key string) (string, error) { if len(collection) == 0 { - return "" + return "", nil } anyValue, ok := collection[key] - if !ok || isNil(anyValue) { - return "" + if !ok || anyValue == nil { + return "", nil } if arg, ok := anyValue.(string); ok { - return arg + return arg, nil } - return "" + if arg, ok := anyValue.(*string); ok { + if arg == nil { + return "", nil + } + + return *arg, nil + } + + return "", fmt.Errorf("expected string, got %v", anyValue) } func unmarshalStringFromJsonMap(collection map[string]json.RawMessage, key string, required bool) (string, error) { diff --git a/utils/environment.go b/utils/environment.go index 41158f71..008888f3 100644 --- a/utils/environment.go +++ b/utils/environment.go @@ -16,8 +16,8 @@ var ( // EnvString represents either a literal string or an environment reference type EnvString struct { - Value *string `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value *string `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvString creates an EnvString instance @@ -101,8 +101,8 @@ func (ev EnvString) GetOrDefault(defaultValue string) (string, error) { // EnvInt represents either a literal integer or an environment reference type EnvInt struct { - Value *int64 `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value *int64 `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvInt creates an EnvInt instance. @@ -175,8 +175,8 @@ func (ev EnvInt) GetOrDefault(defaultValue int64) (int64, error) { // EnvBool represents either a literal boolean or an environment reference type EnvBool struct { - Value *bool `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value *bool `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvBool creates an EnvBool instance. @@ -249,8 +249,8 @@ func (ev EnvBool) GetOrDefault(defaultValue bool) (bool, error) { // EnvFloat represents either a literal floating point number or an environment reference type EnvFloat struct { - Value *float64 `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value *float64 `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvFloat creates an EnvFloat instance. @@ -342,8 +342,8 @@ func validateEnvironmentMapValue(variable *string) error { // EnvMapString represents either a literal string map or an environment reference type EnvMapString struct { - Value map[string]string `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value map[string]string `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvMapString creates an EnvMapString instance. @@ -399,8 +399,8 @@ func (ev EnvMapString) Get() (map[string]string, error) { // EnvMapInt represents either a literal int map or an environment reference type EnvMapInt struct { - Value map[string]int64 `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value map[string]int64 `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvMapInt creates an EnvMapInt instance. @@ -456,8 +456,8 @@ func (ev EnvMapInt) Get() (map[string]int64, error) { // EnvMapFloat represents either a literal float map or an environment reference type EnvMapFloat struct { - Value map[string]float64 `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value map[string]float64 `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvMapFloat creates an EnvMapFloat instance. @@ -513,8 +513,8 @@ func (ev EnvMapFloat) Get() (map[string]float64, error) { // EnvMapBool represents either a literal bool map or an environment reference type EnvMapBool struct { - Value map[string]bool `json:"value,omitempty" yaml:"value,omitempty" jsonschema:"anyof_required=value"` - Variable *string `json:"env,omitempty" yaml:"env,omitempty" jsonschema:"anyof_required=env"` + Value map[string]bool `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value" jsonschema:"anyof_required=value"` + Variable *string `json:"env,omitempty" yaml:"env,omitempty" mapstructure:"env" jsonschema:"anyof_required=env"` } // NewEnvMapBool creates an EnvMapBool instance.