diff --git a/server/go.mod b/server/go.mod index 5a162dca9c..6f2e036434 100644 --- a/server/go.mod +++ b/server/go.mod @@ -25,6 +25,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/labstack/echo/v4 v4.11.4 github.com/oapi-codegen/runtime v1.1.1 + github.com/paulmach/go.geojson v1.5.0 github.com/ravilushqa/otelgqlgen v0.15.0 github.com/reearth/reearthx v0.0.0-20240308140749-72a08570c19b github.com/robbiet480/go.sns v0.0.0-20230523235941-e8d832c79d68 diff --git a/server/go.sum b/server/go.sum index 98ef4b14ef..666b839403 100644 --- a/server/go.sum +++ b/server/go.sum @@ -305,6 +305,8 @@ github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/paulmach/go.geojson v1.5.0 h1:7mhpMK89SQdHFcEGomT7/LuJhwhEgfmpWYVlVmLEdQw= +github.com/paulmach/go.geojson v1.5.0/go.mod h1:DgdUy2rRVDDVgKqrjMe2vZAHMfhDTrjVKt3LmHIXGbU= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= diff --git a/server/internal/adapter/gql/generated.go b/server/internal/adapter/gql/generated.go index 445341901a..fc79300ed3 100644 --- a/server/internal/adapter/gql/generated.go +++ b/server/internal/adapter/gql/generated.go @@ -662,6 +662,11 @@ type ComplexityRoot struct { DefaultValue func(childComplexity int) int } + SchemaFieldGeometry struct { + DefaultValue func(childComplexity int) int + SupportedTypes func(childComplexity int) int + } + SchemaFieldGroup struct { GroupID func(childComplexity int) int } @@ -677,10 +682,6 @@ type ComplexityRoot struct { MaxLength func(childComplexity int) int } - SchemaFieldPoint struct { - DefaultValue func(childComplexity int) int - } - SchemaFieldReference struct { CorrespondingField func(childComplexity int) int CorrespondingFieldID func(childComplexity int) int @@ -3918,6 +3919,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SchemaFieldDate.DefaultValue(childComplexity), true + case "SchemaFieldGeometry.defaultValue": + if e.complexity.SchemaFieldGeometry.DefaultValue == nil { + break + } + + return e.complexity.SchemaFieldGeometry.DefaultValue(childComplexity), true + + case "SchemaFieldGeometry.supportedTypes": + if e.complexity.SchemaFieldGeometry.SupportedTypes == nil { + break + } + + return e.complexity.SchemaFieldGeometry.SupportedTypes(childComplexity), true + case "SchemaFieldGroup.groupId": if e.complexity.SchemaFieldGroup.GroupID == nil { break @@ -3960,13 +3975,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SchemaFieldMarkdown.MaxLength(childComplexity), true - case "SchemaFieldPoint.defaultValue": - if e.complexity.SchemaFieldPoint.DefaultValue == nil { - break - } - - return e.complexity.SchemaFieldPoint.DefaultValue(childComplexity), true - case "SchemaFieldReference.correspondingField": if e.complexity.SchemaFieldReference.CorrespondingField == nil { break @@ -4717,9 +4725,10 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputSchemaFieldBoolInput, ec.unmarshalInputSchemaFieldCheckboxInput, ec.unmarshalInputSchemaFieldDateInput, + ec.unmarshalInputSchemaFieldGeometryInput, ec.unmarshalInputSchemaFieldGroupInput, ec.unmarshalInputSchemaFieldIntegerInput, - ec.unmarshalInputSchemaFieldPointInput, + ec.unmarshalInputSchemaFieldLineStringInput, ec.unmarshalInputSchemaFieldReferenceInput, ec.unmarshalInputSchemaFieldRichTextInput, ec.unmarshalInputSchemaFieldSelectInput, @@ -5125,7 +5134,7 @@ extend type Mutation { Checkbox URL Group - Point + Geometry } enum SchemaFieldTagColor { @@ -5142,6 +5151,16 @@ enum SchemaFieldTagColor { PURPLE } +enum GeometrySupportedType { + POINT + MULTIPOINT + LINESTRING + MULTILINESTRING + POLYGON + MULTIPOLYGON + GEOMETRYCOLLECTION +} + type SchemaField { id: ID! modelId: ID @@ -5179,7 +5198,7 @@ union SchemaFieldTypeProperty = | SchemaFieldURL | SchemaFieldCheckbox | SchemaFieldGroup - | SchemaFieldPoint + | SchemaFieldGeometry type SchemaFieldText { defaultValue: Any @@ -5255,8 +5274,9 @@ type SchemaFieldGroup { groupId: ID! } -type SchemaFieldPoint { +type SchemaFieldGeometry { defaultValue: Any + supportedTypes: [GeometrySupportedType!]! } # Inputs @@ -5341,7 +5361,12 @@ input SchemaFieldGroupInput { groupId: ID! } -input SchemaFieldPointInput { +input SchemaFieldGeometryInput { + defaultValue: Any + supportedTypes: [GeometrySupportedType!]! +} + +input SchemaFieldLineStringInput { defaultValue: Any } @@ -5360,7 +5385,7 @@ input SchemaFieldTypePropertyInput @onlyOne { reference: SchemaFieldReferenceInput url: SchemaFieldURLInput group: SchemaFieldGroupInput - point: SchemaFieldPointInput + geometry: SchemaFieldGeometryInput } input CreateFieldInput { @@ -26480,6 +26505,91 @@ func (ec *executionContext) fieldContext_SchemaFieldDate_defaultValue(ctx contex return fc, nil } +func (ec *executionContext) _SchemaFieldGeometry_defaultValue(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldGeometry) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SchemaFieldGeometry_defaultValue(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DefaultValue, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(interface{}) + fc.Result = res + return ec.marshalOAny2interface(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SchemaFieldGeometry_defaultValue(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SchemaFieldGeometry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Any does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _SchemaFieldGeometry_supportedTypes(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldGeometry) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SchemaFieldGeometry_supportedTypes(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SupportedTypes, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]gqlmodel.GeometrySupportedType) + fc.Result = res + return ec.marshalNGeometrySupportedType2ᚕgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedTypeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SchemaFieldGeometry_supportedTypes(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SchemaFieldGeometry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type GeometrySupportedType does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _SchemaFieldGroup_groupId(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldGroup) (ret graphql.Marshaler) { fc, err := ec.fieldContext_SchemaFieldGroup_groupId(ctx, field) if err != nil { @@ -26729,47 +26839,6 @@ func (ec *executionContext) fieldContext_SchemaFieldMarkdown_maxLength(ctx conte return fc, nil } -func (ec *executionContext) _SchemaFieldPoint_defaultValue(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldPoint) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SchemaFieldPoint_defaultValue(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.DefaultValue, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(interface{}) - fc.Result = res - return ec.marshalOAny2interface(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_SchemaFieldPoint_defaultValue(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "SchemaFieldPoint", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Any does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _SchemaFieldReference_modelId(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldReference) (ret graphql.Marshaler) { fc, err := ec.fieldContext_SchemaFieldReference_modelId(ctx, field) if err != nil { @@ -36036,6 +36105,40 @@ func (ec *executionContext) unmarshalInputSchemaFieldDateInput(ctx context.Conte return it, nil } +func (ec *executionContext) unmarshalInputSchemaFieldGeometryInput(ctx context.Context, obj interface{}) (gqlmodel.SchemaFieldGeometryInput, error) { + var it gqlmodel.SchemaFieldGeometryInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"defaultValue", "supportedTypes"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "defaultValue": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("defaultValue")) + data, err := ec.unmarshalOAny2interface(ctx, v) + if err != nil { + return it, err + } + it.DefaultValue = data + case "supportedTypes": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("supportedTypes")) + data, err := ec.unmarshalNGeometrySupportedType2ᚕgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedTypeᚄ(ctx, v) + if err != nil { + return it, err + } + it.SupportedTypes = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputSchemaFieldGroupInput(ctx context.Context, obj interface{}) (gqlmodel.SchemaFieldGroupInput, error) { var it gqlmodel.SchemaFieldGroupInput asMap := map[string]interface{}{} @@ -36104,8 +36207,8 @@ func (ec *executionContext) unmarshalInputSchemaFieldIntegerInput(ctx context.Co return it, nil } -func (ec *executionContext) unmarshalInputSchemaFieldPointInput(ctx context.Context, obj interface{}) (gqlmodel.SchemaFieldPointInput, error) { - var it gqlmodel.SchemaFieldPointInput +func (ec *executionContext) unmarshalInputSchemaFieldLineStringInput(ctx context.Context, obj interface{}) (gqlmodel.SchemaFieldLineStringInput, error) { + var it gqlmodel.SchemaFieldLineStringInput asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v @@ -36390,7 +36493,7 @@ func (ec *executionContext) unmarshalInputSchemaFieldTypePropertyInput(ctx conte asMap[k] = v } - fieldsInOrder := [...]string{"text", "textArea", "richText", "markdownText", "asset", "date", "bool", "select", "tag", "checkbox", "integer", "reference", "url", "group", "point"} + fieldsInOrder := [...]string{"text", "textArea", "richText", "markdownText", "asset", "date", "bool", "select", "tag", "checkbox", "integer", "reference", "url", "group", "geometry"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -36733,10 +36836,10 @@ func (ec *executionContext) unmarshalInputSchemaFieldTypePropertyInput(ctx conte err := fmt.Errorf(`unexpected type %T from directive, should be *github.com/reearth/reearth-cms/server/internal/adapter/gql/gqlmodel.SchemaFieldGroupInput`, tmp) return it, graphql.ErrorOnPath(ctx, err) } - case "point": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("point")) + case "geometry": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("geometry")) directive0 := func(ctx context.Context) (interface{}, error) { - return ec.unmarshalOSchemaFieldPointInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldPointInput(ctx, v) + return ec.unmarshalOSchemaFieldGeometryInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldGeometryInput(ctx, v) } directive1 := func(ctx context.Context) (interface{}, error) { if ec.directives.OnlyOne == nil { @@ -36749,12 +36852,12 @@ func (ec *executionContext) unmarshalInputSchemaFieldTypePropertyInput(ctx conte if err != nil { return it, graphql.ErrorOnPath(ctx, err) } - if data, ok := tmp.(*gqlmodel.SchemaFieldPointInput); ok { - it.Point = data + if data, ok := tmp.(*gqlmodel.SchemaFieldGeometryInput); ok { + it.Geometry = data } else if tmp == nil { - it.Point = nil + it.Geometry = nil } else { - err := fmt.Errorf(`unexpected type %T from directive, should be *github.com/reearth/reearth-cms/server/internal/adapter/gql/gqlmodel.SchemaFieldPointInput`, tmp) + err := fmt.Errorf(`unexpected type %T from directive, should be *github.com/reearth/reearth-cms/server/internal/adapter/gql/gqlmodel.SchemaFieldGeometryInput`, tmp) return it, graphql.ErrorOnPath(ctx, err) } } @@ -38556,13 +38659,13 @@ func (ec *executionContext) _SchemaFieldTypeProperty(ctx context.Context, sel as return graphql.Null } return ec._SchemaFieldGroup(ctx, sel, obj) - case gqlmodel.SchemaFieldPoint: - return ec._SchemaFieldPoint(ctx, sel, &obj) - case *gqlmodel.SchemaFieldPoint: + case gqlmodel.SchemaFieldGeometry: + return ec._SchemaFieldGeometry(ctx, sel, &obj) + case *gqlmodel.SchemaFieldGeometry: if obj == nil { return graphql.Null } - return ec._SchemaFieldPoint(ctx, sel, obj) + return ec._SchemaFieldGeometry(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) } @@ -44237,19 +44340,21 @@ func (ec *executionContext) _SchemaFieldDate(ctx context.Context, sel ast.Select return out } -var schemaFieldGroupImplementors = []string{"SchemaFieldGroup", "SchemaFieldTypeProperty"} +var schemaFieldGeometryImplementors = []string{"SchemaFieldGeometry", "SchemaFieldTypeProperty"} -func (ec *executionContext) _SchemaFieldGroup(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldGroup) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldGroupImplementors) +func (ec *executionContext) _SchemaFieldGeometry(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldGeometry) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldGeometryImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SchemaFieldGroup") - case "groupId": - out.Values[i] = ec._SchemaFieldGroup_groupId(ctx, field, obj) + out.Values[i] = graphql.MarshalString("SchemaFieldGeometry") + case "defaultValue": + out.Values[i] = ec._SchemaFieldGeometry_defaultValue(ctx, field, obj) + case "supportedTypes": + out.Values[i] = ec._SchemaFieldGeometry_supportedTypes(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -44276,23 +44381,22 @@ func (ec *executionContext) _SchemaFieldGroup(ctx context.Context, sel ast.Selec return out } -var schemaFieldIntegerImplementors = []string{"SchemaFieldInteger", "SchemaFieldTypeProperty"} +var schemaFieldGroupImplementors = []string{"SchemaFieldGroup", "SchemaFieldTypeProperty"} -func (ec *executionContext) _SchemaFieldInteger(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldInteger) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldIntegerImplementors) +func (ec *executionContext) _SchemaFieldGroup(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldGroup) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldGroupImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SchemaFieldInteger") - case "defaultValue": - out.Values[i] = ec._SchemaFieldInteger_defaultValue(ctx, field, obj) - case "min": - out.Values[i] = ec._SchemaFieldInteger_min(ctx, field, obj) - case "max": - out.Values[i] = ec._SchemaFieldInteger_max(ctx, field, obj) + out.Values[i] = graphql.MarshalString("SchemaFieldGroup") + case "groupId": + out.Values[i] = ec._SchemaFieldGroup_groupId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -44316,21 +44420,23 @@ func (ec *executionContext) _SchemaFieldInteger(ctx context.Context, sel ast.Sel return out } -var schemaFieldMarkdownImplementors = []string{"SchemaFieldMarkdown", "SchemaFieldTypeProperty"} +var schemaFieldIntegerImplementors = []string{"SchemaFieldInteger", "SchemaFieldTypeProperty"} -func (ec *executionContext) _SchemaFieldMarkdown(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldMarkdown) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldMarkdownImplementors) +func (ec *executionContext) _SchemaFieldInteger(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldInteger) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldIntegerImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SchemaFieldMarkdown") + out.Values[i] = graphql.MarshalString("SchemaFieldInteger") case "defaultValue": - out.Values[i] = ec._SchemaFieldMarkdown_defaultValue(ctx, field, obj) - case "maxLength": - out.Values[i] = ec._SchemaFieldMarkdown_maxLength(ctx, field, obj) + out.Values[i] = ec._SchemaFieldInteger_defaultValue(ctx, field, obj) + case "min": + out.Values[i] = ec._SchemaFieldInteger_min(ctx, field, obj) + case "max": + out.Values[i] = ec._SchemaFieldInteger_max(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -44354,19 +44460,21 @@ func (ec *executionContext) _SchemaFieldMarkdown(ctx context.Context, sel ast.Se return out } -var schemaFieldPointImplementors = []string{"SchemaFieldPoint", "SchemaFieldTypeProperty"} +var schemaFieldMarkdownImplementors = []string{"SchemaFieldMarkdown", "SchemaFieldTypeProperty"} -func (ec *executionContext) _SchemaFieldPoint(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldPoint) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldPointImplementors) +func (ec *executionContext) _SchemaFieldMarkdown(ctx context.Context, sel ast.SelectionSet, obj *gqlmodel.SchemaFieldMarkdown) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, schemaFieldMarkdownImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SchemaFieldPoint") + out.Values[i] = graphql.MarshalString("SchemaFieldMarkdown") case "defaultValue": - out.Values[i] = ec._SchemaFieldPoint_defaultValue(ctx, field, obj) + out.Values[i] = ec._SchemaFieldMarkdown_defaultValue(ctx, field, obj) + case "maxLength": + out.Values[i] = ec._SchemaFieldMarkdown_maxLength(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -47034,6 +47142,77 @@ func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.S return graphql.WrapContextMarshaler(ctx, res) } +func (ec *executionContext) unmarshalNGeometrySupportedType2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedType(ctx context.Context, v interface{}) (gqlmodel.GeometrySupportedType, error) { + var res gqlmodel.GeometrySupportedType + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNGeometrySupportedType2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedType(ctx context.Context, sel ast.SelectionSet, v gqlmodel.GeometrySupportedType) graphql.Marshaler { + return v +} + +func (ec *executionContext) unmarshalNGeometrySupportedType2ᚕgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedTypeᚄ(ctx context.Context, v interface{}) ([]gqlmodel.GeometrySupportedType, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]gqlmodel.GeometrySupportedType, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNGeometrySupportedType2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedType(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNGeometrySupportedType2ᚕgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []gqlmodel.GeometrySupportedType) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNGeometrySupportedType2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGeometrySupportedType(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNGroup2ᚕᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐGroup(ctx context.Context, sel ast.SelectionSet, v []*gqlmodel.Group) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -50426,27 +50605,27 @@ func (ec *executionContext) unmarshalOSchemaFieldDateInput2ᚖgithubᚗcomᚋree return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOSchemaFieldGroupInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldGroupInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldGroupInput, error) { +func (ec *executionContext) unmarshalOSchemaFieldGeometryInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldGeometryInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldGeometryInput, error) { if v == nil { return nil, nil } - res, err := ec.unmarshalInputSchemaFieldGroupInput(ctx, v) + res, err := ec.unmarshalInputSchemaFieldGeometryInput(ctx, v) return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOSchemaFieldIntegerInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldIntegerInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldIntegerInput, error) { +func (ec *executionContext) unmarshalOSchemaFieldGroupInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldGroupInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldGroupInput, error) { if v == nil { return nil, nil } - res, err := ec.unmarshalInputSchemaFieldIntegerInput(ctx, v) + res, err := ec.unmarshalInputSchemaFieldGroupInput(ctx, v) return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOSchemaFieldPointInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldPointInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldPointInput, error) { +func (ec *executionContext) unmarshalOSchemaFieldIntegerInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchemaFieldIntegerInput(ctx context.Context, v interface{}) (*gqlmodel.SchemaFieldIntegerInput, error) { if v == nil { return nil, nil } - res, err := ec.unmarshalInputSchemaFieldPointInput(ctx, v) + res, err := ec.unmarshalInputSchemaFieldIntegerInput(ctx, v) return &res, graphql.ErrorOnPath(ctx, err) } diff --git a/server/internal/adapter/gql/gqlmodel/convert_schema.go b/server/internal/adapter/gql/gqlmodel/convert_schema.go index 87cb82cc21..f06d404852 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_schema.go +++ b/server/internal/adapter/gql/gqlmodel/convert_schema.go @@ -80,6 +80,50 @@ func ToSchemaFieldTagColor(c schema.TagColor) SchemaFieldTagColor { } +func ToGeometrySupportedType(g schema.GeometrySupportedType) GeometrySupportedType { + switch g { + case schema.GeometrySupportedTypePoint: + return GeometrySupportedTypePoint + case schema.GeometrySupportedTypeMultiPoint: + return GeometrySupportedTypeMultipoint + case schema.GeometrySupportedTypeLineString: + return GeometrySupportedTypeLinestring + case schema.GeometrySupportedTypeMultiLineString: + return GeometrySupportedTypeMultilinestring + case schema.GeometrySupportedTypePolygon: + return GeometrySupportedTypePolygon + case schema.GeometrySupportedTypeMultiPolygon: + return GeometrySupportedTypeMultipolygon + case schema.GeometrySupportedTypeGeometryCollection: + return GeometrySupportedTypeGeometrycollection + + default: + return "" + } +} + +func FromGeometrySupportedType(g GeometrySupportedType) schema.GeometrySupportedType { + switch g { + case GeometrySupportedTypePoint: + return schema.GeometrySupportedTypePoint + case GeometrySupportedTypeMultipoint: + return schema.GeometrySupportedTypeMultiPoint + case GeometrySupportedTypeLinestring: + return schema.GeometrySupportedTypeLineString + case GeometrySupportedTypeMultilinestring: + return schema.GeometrySupportedTypeMultiLineString + case GeometrySupportedTypePolygon: + return schema.GeometrySupportedTypePolygon + case GeometrySupportedTypeMultipolygon: + return schema.GeometrySupportedTypeMultiPolygon + case GeometrySupportedTypeGeometrycollection: + return schema.GeometrySupportedTypeGeometryCollection + + default: + return "" + } +} + func ToSchemaFieldTypeProperty(tp *schema.TypeProperty, dv *value.Multiple, multiple bool) (res SchemaFieldTypeProperty) { tp.Match(schema.TypePropertyMatch{ Text: func(f *schema.FieldText) { @@ -234,17 +278,18 @@ func ToSchemaFieldTypeProperty(tp *schema.TypeProperty, dv *value.Multiple, mult DefaultValue: v, } }, - Point: func(f *schema.FieldPoint) { + Geometry: func(f *schema.FieldGeometry) { var v any = nil if dv != nil { if multiple { - v, _ = dv.ValuesPosition() + v, _ = dv.ValuesString() } else { - v, _ = dv.First().ValuePosition() + v, _ = dv.First().ValueString() } } - res = &SchemaFieldPoint{ - DefaultValue: v, + res = &SchemaFieldGeometry{ + DefaultValue: v, + SupportedTypes: lo.Map(f.SupportedTypes(), func(v schema.GeometrySupportedType, _ int) GeometrySupportedType { return ToGeometrySupportedType(v) }), } }, }) @@ -503,17 +548,19 @@ func FromSchemaTypeProperty(tp *SchemaFieldTypePropertyInput, t SchemaFieldType, dv = FromValue(SchemaFieldTypeURL, x.DefaultValue).AsMultiple() } tpRes = schema.NewURL().TypeProperty() - case SchemaFieldTypePoint: - x := tp.Point + case SchemaFieldTypeGeometry: + x := tp.Geometry if x == nil { return nil, nil, ErrInvalidTypeProperty } if multiple { - dv = value.NewMultiple(value.TypePoint, unpackArray(x.DefaultValue)) + dv = value.NewMultiple(value.TypeGeometry, unpackArray(x.DefaultValue)) } else { - dv = FromValue(SchemaFieldTypePoint, x.DefaultValue).AsMultiple() + dv = FromValue(SchemaFieldTypeGeometry, x.DefaultValue).AsMultiple() } - tpRes = schema.NewPoint().TypeProperty() + tpRes = schema.NewGeometry(lo.Map(x.SupportedTypes, func(v GeometrySupportedType, _ int) schema.GeometrySupportedType { + return FromGeometrySupportedType(v) + })).TypeProperty() default: return nil, nil, ErrInvalidTypeProperty } diff --git a/server/internal/adapter/gql/gqlmodel/convert_schema_test.go b/server/internal/adapter/gql/gqlmodel/convert_schema_test.go index d279c16066..739066047f 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_schema_test.go +++ b/server/internal/adapter/gql/gqlmodel/convert_schema_test.go @@ -210,9 +210,9 @@ func TestToSchemaFieldTypeProperty(t *testing.T) { want: &SchemaFieldSelect{Values: []string{"v1"}, DefaultValue: nil}, }, { - name: "point", - args: args{tp: schema.NewPoint().TypeProperty()}, - want: &SchemaFieldPoint{DefaultValue: nil}, + name: "geometry", + args: args{tp: schema.NewGeometry(schema.GeometrySupportedTypeList{"POINT"}).TypeProperty()}, + want: &SchemaFieldGeometry{SupportedTypes: []GeometrySupportedType{"POINT"}, DefaultValue: nil}, }, } for _, tt := range tests { @@ -336,12 +336,12 @@ func TestFromSchemaFieldTypeProperty(t *testing.T) { wantError: ErrEmptyOptions, }, { - name: "point", + name: "geometry", argsInp: &SchemaFieldTypePropertyInput{ - Point: &SchemaFieldPointInput{DefaultValue: nil}, + Geometry: &SchemaFieldGeometryInput{SupportedTypes: []GeometrySupportedType{"POINT"}, DefaultValue: nil}, }, - argsT: SchemaFieldTypePoint, - wantTp: schema.NewPoint().TypeProperty(), + argsT: SchemaFieldTypeGeometry, + wantTp: schema.NewGeometry(schema.GeometrySupportedTypeList{"POINT"}).TypeProperty(), }, { name: "tags empty", @@ -390,3 +390,117 @@ func TestFromCorrespondingField(t *testing.T) { got = FromCorrespondingField(cf) assert.Equal(t, want, got) } + +func TestToGeometrySupportedType(t *testing.T) { + tests := []struct { + name string + arg schema.GeometrySupportedType + want GeometrySupportedType + }{ + { + name: "point", + arg: schema.GeometrySupportedTypePoint, + want: GeometrySupportedTypePoint, + }, + { + name: "multiPoint", + arg: schema.GeometrySupportedTypeMultiPoint, + want: GeometrySupportedTypeMultipoint, + }, + { + name: "lineString", + arg: schema.GeometrySupportedTypeLineString, + want: GeometrySupportedTypeLinestring, + }, + { + name: "multiLineString", + arg: schema.GeometrySupportedTypeMultiLineString, + want: GeometrySupportedTypeMultilinestring, + }, + { + name: "polygon", + arg: schema.GeometrySupportedTypePolygon, + want: GeometrySupportedTypePolygon, + }, + { + name: "multiPolygon", + arg: schema.GeometrySupportedTypeMultiPolygon, + want: GeometrySupportedTypeMultipolygon, + }, + { + name: "geometryCollection", + arg: schema.GeometrySupportedTypeGeometryCollection, + want: GeometrySupportedTypeGeometrycollection, + }, + { + name: "default", + arg: "foo", + want: "", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + assert.Equal(tt, tc.want, ToGeometrySupportedType(tc.arg)) + }) + } +} + +func TestFromGeometrySupportedType(t *testing.T) { + tests := []struct { + name string + arg GeometrySupportedType + want schema.GeometrySupportedType + }{ + { + name: "point", + arg: GeometrySupportedTypePoint, + want: schema.GeometrySupportedTypePoint, + }, + { + name: "multiPoint", + arg: GeometrySupportedTypeMultipoint, + want: schema.GeometrySupportedTypeMultiPoint, + }, + { + name: "lineString", + arg: GeometrySupportedTypeLinestring, + want: schema.GeometrySupportedTypeLineString, + }, + { + name: "multiLineString", + arg: GeometrySupportedTypeMultilinestring, + want: schema.GeometrySupportedTypeMultiLineString, + }, + { + name: "polygon", + arg: GeometrySupportedTypePolygon, + want: schema.GeometrySupportedTypePolygon, + }, + { + name: "multiPolygon", + arg: GeometrySupportedTypeMultipolygon, + want: schema.GeometrySupportedTypeMultiPolygon, + }, + { + name: "geometryCollection", + arg: GeometrySupportedTypeGeometrycollection, + want: schema.GeometrySupportedTypeGeometryCollection, + }, + { + name: "default", + arg: "foo", + want: "", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + assert.Equal(tt, tc.want, FromGeometrySupportedType(tc.arg)) + }) + } +} diff --git a/server/internal/adapter/gql/gqlmodel/models_gen.go b/server/internal/adapter/gql/gqlmodel/models_gen.go index d4424648fc..16ebb0184b 100644 --- a/server/internal/adapter/gql/gqlmodel/models_gen.go +++ b/server/internal/adapter/gql/gqlmodel/models_gen.go @@ -969,6 +969,18 @@ type SchemaFieldDateInput struct { DefaultValue interface{} `json:"defaultValue,omitempty"` } +type SchemaFieldGeometry struct { + DefaultValue interface{} `json:"defaultValue,omitempty"` + SupportedTypes []GeometrySupportedType `json:"supportedTypes"` +} + +func (SchemaFieldGeometry) IsSchemaFieldTypeProperty() {} + +type SchemaFieldGeometryInput struct { + DefaultValue interface{} `json:"defaultValue,omitempty"` + SupportedTypes []GeometrySupportedType `json:"supportedTypes"` +} + type SchemaFieldGroup struct { GroupID ID `json:"groupId"` } @@ -993,22 +1005,16 @@ type SchemaFieldIntegerInput struct { Max *int `json:"max,omitempty"` } -type SchemaFieldMarkdown struct { +type SchemaFieldLineStringInput struct { DefaultValue interface{} `json:"defaultValue,omitempty"` - MaxLength *int `json:"maxLength,omitempty"` } -func (SchemaFieldMarkdown) IsSchemaFieldTypeProperty() {} - -type SchemaFieldPoint struct { +type SchemaFieldMarkdown struct { DefaultValue interface{} `json:"defaultValue,omitempty"` + MaxLength *int `json:"maxLength,omitempty"` } -func (SchemaFieldPoint) IsSchemaFieldTypeProperty() {} - -type SchemaFieldPointInput struct { - DefaultValue interface{} `json:"defaultValue,omitempty"` -} +func (SchemaFieldMarkdown) IsSchemaFieldTypeProperty() {} type SchemaFieldReference struct { ModelID ID `json:"modelId"` @@ -1113,7 +1119,7 @@ type SchemaFieldTypePropertyInput struct { Reference *SchemaFieldReferenceInput `json:"reference,omitempty"` URL *SchemaFieldURLInput `json:"url,omitempty"` Group *SchemaFieldGroupInput `json:"group,omitempty"` - Point *SchemaFieldPointInput `json:"point,omitempty"` + Geometry *SchemaFieldGeometryInput `json:"geometry,omitempty"` } type SchemaFieldURL struct { @@ -1734,6 +1740,57 @@ func (e FieldType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } +type GeometrySupportedType string + +const ( + GeometrySupportedTypePoint GeometrySupportedType = "POINT" + GeometrySupportedTypeMultipoint GeometrySupportedType = "MULTIPOINT" + GeometrySupportedTypeLinestring GeometrySupportedType = "LINESTRING" + GeometrySupportedTypeMultilinestring GeometrySupportedType = "MULTILINESTRING" + GeometrySupportedTypePolygon GeometrySupportedType = "POLYGON" + GeometrySupportedTypeMultipolygon GeometrySupportedType = "MULTIPOLYGON" + GeometrySupportedTypeGeometrycollection GeometrySupportedType = "GEOMETRYCOLLECTION" +) + +var AllGeometrySupportedType = []GeometrySupportedType{ + GeometrySupportedTypePoint, + GeometrySupportedTypeMultipoint, + GeometrySupportedTypeLinestring, + GeometrySupportedTypeMultilinestring, + GeometrySupportedTypePolygon, + GeometrySupportedTypeMultipolygon, + GeometrySupportedTypeGeometrycollection, +} + +func (e GeometrySupportedType) IsValid() bool { + switch e { + case GeometrySupportedTypePoint, GeometrySupportedTypeMultipoint, GeometrySupportedTypeLinestring, GeometrySupportedTypeMultilinestring, GeometrySupportedTypePolygon, GeometrySupportedTypeMultipolygon, GeometrySupportedTypeGeometrycollection: + return true + } + return false +} + +func (e GeometrySupportedType) String() string { + return string(e) +} + +func (e *GeometrySupportedType) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = GeometrySupportedType(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid GeometrySupportedType", str) + } + return nil +} + +func (e GeometrySupportedType) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + type IntegrationType string const ( @@ -2317,7 +2374,7 @@ const ( SchemaFieldTypeCheckbox SchemaFieldType = "Checkbox" SchemaFieldTypeURL SchemaFieldType = "URL" SchemaFieldTypeGroup SchemaFieldType = "Group" - SchemaFieldTypePoint SchemaFieldType = "Point" + SchemaFieldTypeGeometry SchemaFieldType = "Geometry" ) var AllSchemaFieldType = []SchemaFieldType{ @@ -2335,12 +2392,12 @@ var AllSchemaFieldType = []SchemaFieldType{ SchemaFieldTypeCheckbox, SchemaFieldTypeURL, SchemaFieldTypeGroup, - SchemaFieldTypePoint, + SchemaFieldTypeGeometry, } func (e SchemaFieldType) IsValid() bool { switch e { - case SchemaFieldTypeText, SchemaFieldTypeTextArea, SchemaFieldTypeRichText, SchemaFieldTypeMarkdownText, SchemaFieldTypeAsset, SchemaFieldTypeDate, SchemaFieldTypeBool, SchemaFieldTypeSelect, SchemaFieldTypeTag, SchemaFieldTypeInteger, SchemaFieldTypeReference, SchemaFieldTypeCheckbox, SchemaFieldTypeURL, SchemaFieldTypeGroup, SchemaFieldTypePoint: + case SchemaFieldTypeText, SchemaFieldTypeTextArea, SchemaFieldTypeRichText, SchemaFieldTypeMarkdownText, SchemaFieldTypeAsset, SchemaFieldTypeDate, SchemaFieldTypeBool, SchemaFieldTypeSelect, SchemaFieldTypeTag, SchemaFieldTypeInteger, SchemaFieldTypeReference, SchemaFieldTypeCheckbox, SchemaFieldTypeURL, SchemaFieldTypeGroup, SchemaFieldTypeGeometry: return true } return false diff --git a/server/pkg/schema/field_geometry.go b/server/pkg/schema/field_geometry.go new file mode 100644 index 0000000000..7e79f5e9ff --- /dev/null +++ b/server/pkg/schema/field_geometry.go @@ -0,0 +1,92 @@ +package schema + +import ( + "encoding/json" + "strings" + + geojson "github.com/paulmach/go.geojson" + "github.com/reearth/reearth-cms/server/pkg/value" + "golang.org/x/exp/slices" +) + +type GeometrySupportedTypeList []GeometrySupportedType + +type FieldGeometry struct { + st GeometrySupportedTypeList +} + +func NewGeometry(supportedTypes GeometrySupportedTypeList) *FieldGeometry { + return &FieldGeometry{ + st: supportedTypes, + } +} + +func (f *FieldGeometry) TypeProperty() *TypeProperty { + return &TypeProperty{ + t: f.Type(), + geometry: f, + } +} + +func (f *FieldGeometry) SupportedTypes() GeometrySupportedTypeList { + return slices.Clone(f.st) +} + +func (f *FieldGeometry) Type() value.Type { + return value.TypeGeometry +} + +func (f *FieldGeometry) Clone() *FieldGeometry { + if f == nil { + return nil + } + return &FieldGeometry{ + st: f.SupportedTypes(), + } +} + +// IsValidGeoJSON uses the go.geojson library to validate a GeoJSON string +func IsValidGeoJSON(data string) bool { + if len(strings.TrimSpace(data)) == 0 { + return false + } + + var raw map[string]interface{} + if err := json.Unmarshal([]byte(data), &raw); err != nil { + return false + } + + geoType, ok := raw["type"].(string) + if !ok { + return false + } + + switch geoType { + case "Feature": + _, err := geojson.UnmarshalFeature([]byte(data)) + return err == nil + case "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon", "GeometryCollection": + _, err := geojson.UnmarshalGeometry([]byte(data)) + return err == nil + default: + return false + } +} + +func (f *FieldGeometry) Validate(v *value.Value) (err error) { + v.Match(value.Match{ + Geometry: func(a value.String) { + if !IsValidGeoJSON(a) { + err = ErrInvalidValue + } + }, + Default: func() { + err = ErrInvalidValue + }, + }) + return +} + +func (f *FieldGeometry) ValidateMultiple(v *value.Multiple) (err error) { + return nil +} diff --git a/server/pkg/schema/field_geometry_supported_type_test.go b/server/pkg/schema/field_geometry_supported_type_test.go new file mode 100644 index 0000000000..7c9db7bea9 --- /dev/null +++ b/server/pkg/schema/field_geometry_supported_type_test.go @@ -0,0 +1,69 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGeometrySupportedTypeFrom(t *testing.T) { + tests := []struct { + name string + arg string + want GeometrySupportedType + }{ + { + name: "point", + arg: "point", + want: GeometrySupportedTypePoint, + }, + { + name: "point", + arg: "POINT", + want: GeometrySupportedTypePoint, + }, + { + name: "multiPoint", + arg: "MULTIPOINT", + want: GeometrySupportedTypeMultiPoint, + }, + { + name: "lineString", + arg: "LINESTRING", + want: GeometrySupportedTypeLineString, + }, + { + name: "multiLineString", + arg: "MULTILINESTRING", + want: GeometrySupportedTypeMultiLineString, + }, + { + name: "polygon", + arg: "POLYGON", + want: GeometrySupportedTypePolygon, + }, + { + name: "multiPolygon", + arg: "MULTIPOLYGON", + want: GeometrySupportedTypeMultiPolygon, + }, + { + name: "geometryCollection", + arg: "GEOMETRYCOLLECTION", + want: GeometrySupportedTypeGeometryCollection, + }, + { + name: "default", + arg: "foo", + want: "", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + assert.Equal(tt, tc.want, GeometrySupportedTypeFrom(tc.arg)) + }) + } +} diff --git a/server/pkg/schema/field_geometry_supported_types.go b/server/pkg/schema/field_geometry_supported_types.go new file mode 100644 index 0000000000..6d397cfb0d --- /dev/null +++ b/server/pkg/schema/field_geometry_supported_types.go @@ -0,0 +1,42 @@ +package schema + +import "strings" + +type GeometrySupportedType string + +const ( + GeometrySupportedTypePoint GeometrySupportedType = "POINT" + GeometrySupportedTypeMultiPoint GeometrySupportedType = "MULTIPOINT" + GeometrySupportedTypeLineString GeometrySupportedType = "LINESTRING" + GeometrySupportedTypeMultiLineString GeometrySupportedType = "MULTILINESTRING" + GeometrySupportedTypePolygon GeometrySupportedType = "POLYGON" + GeometrySupportedTypeMultiPolygon GeometrySupportedType = "MULTIPOLYGON" + GeometrySupportedTypeGeometryCollection GeometrySupportedType = "GEOMETRYCOLLECTION" +) + +func (s GeometrySupportedType) String() string { + return string(s) +} + +func GeometrySupportedTypeFrom(s string) GeometrySupportedType { + ss := strings.ToUpper(s) + switch GeometrySupportedType(ss) { + case GeometrySupportedTypePoint: + return GeometrySupportedTypePoint + case GeometrySupportedTypeMultiPoint: + return GeometrySupportedTypeMultiPoint + case GeometrySupportedTypeLineString: + return GeometrySupportedTypeLineString + case GeometrySupportedTypeMultiLineString: + return GeometrySupportedTypeMultiLineString + case GeometrySupportedTypePolygon: + return GeometrySupportedTypePolygon + case GeometrySupportedTypeMultiPolygon: + return GeometrySupportedTypeMultiPolygon + case GeometrySupportedTypeGeometryCollection: + return GeometrySupportedTypeGeometryCollection + + default: + return GeometrySupportedType("") + } +} diff --git a/server/pkg/schema/field_geometry_test.go b/server/pkg/schema/field_geometry_test.go new file mode 100644 index 0000000000..4838ec7ee5 --- /dev/null +++ b/server/pkg/schema/field_geometry_test.go @@ -0,0 +1,98 @@ +package schema + +import ( + "testing" + + "github.com/reearth/reearth-cms/server/pkg/value" + "github.com/stretchr/testify/assert" +) + +func TestNewGeometry(t *testing.T) { + expected := &FieldGeometry{ + st: GeometrySupportedTypeList{"POINT"}, + } + res := NewGeometry(GeometrySupportedTypeList{"POINT"}) + assert.Equal(t, expected, res) +} + +func TestFieldGeometry_Type(t *testing.T) { + assert.Equal(t, value.TypeGeometry, (&FieldGeometry{}).Type()) +} + +func TestFieldGeometry_TypeProperty(t *testing.T) { + f := FieldGeometry{} + assert.Equal(t, &TypeProperty{ + t: f.Type(), + geometry: &f, + }, (&f).TypeProperty()) +} +func TestFieldGeometry_Clone(t *testing.T) { + assert.Nil(t, (*FieldGeometry)(nil).Clone()) + assert.Equal(t, &FieldGeometry{}, (&FieldGeometry{}).Clone()) +} + +func TestFieldGeometry_Validate(t *testing.T) { + supportedType := GeometrySupportedTypePoint + geojson := `{ + "type": "Point", + "coordinates": [102.0, 0.5] + }` + assert.NoError(t, (&FieldGeometry{st: GeometrySupportedTypeList{supportedType}}).Validate(value.TypeGeometry.Value(geojson))) + assert.Equal(t, ErrInvalidValue, (&FieldGeometry{}).Validate(value.TypeText.Value("{}"))) + assert.Equal(t, ErrInvalidValue, (&FieldGeometry{}).Validate(value.TypeText.Value(float64(1)))) +} + +func TestIsValidGeoJSON(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + { + name: "valid GeoJSON feature", + input: `{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [102.0, 0.5] + }, + "properties": { + "prop0": "value0" + } + }`, + expected: true, + }, + { + name: "valid GeoJSON geometry", + input: `{ + "type": "Point", + "coordinates": [102.0, 0.5] + }`, + expected: true, + }, + { + name: "invalid GeoJSON type", + input: `{ + "type": "InvalidType", + "coordinates": [102.0, 0.5] + }`, + expected: false, + }, + { + name: "empty string", + input: ``, + expected: false, + }, + { + name: "random string", + input: `random string`, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, IsValidGeoJSON(tt.input)) + }) + } +} diff --git a/server/pkg/schema/field_point.go b/server/pkg/schema/field_point.go deleted file mode 100644 index d8f8767f41..0000000000 --- a/server/pkg/schema/field_point.go +++ /dev/null @@ -1,41 +0,0 @@ -package schema - -import "github.com/reearth/reearth-cms/server/pkg/value" - -type FieldPoint struct { - p *FieldPosition -} - -func NewPoint() *FieldPoint { - return &FieldPoint{ - p: NewPosition(value.TypePoint), - } -} - -func (f *FieldPoint) TypeProperty() *TypeProperty { - return &TypeProperty{ - t: f.Type(), - point: f, - } -} - -func (f *FieldPoint) Type() value.Type { - return value.TypePoint -} - -func (f *FieldPoint) Clone() *FieldPoint { - if f == nil { - return nil - } - return &FieldPoint{ - p: f.p.Clone(), - } -} - -func (f *FieldPoint) Validate(v *value.Value) error { - return f.p.Validate(v) -} - -func (f *FieldPoint) ValidateMultiple(v *value.Multiple) error { - return nil -} diff --git a/server/pkg/schema/field_point_test.go b/server/pkg/schema/field_point_test.go deleted file mode 100644 index c72dcf892c..0000000000 --- a/server/pkg/schema/field_point_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package schema - -import ( - "testing" - - "github.com/reearth/reearth-cms/server/pkg/value" - "github.com/stretchr/testify/assert" -) - -func TestNewPoint(t *testing.T) { - assert.Equal(t, &FieldPoint{p: &FieldPosition{t: value.TypePoint}}, NewPoint()) -} - -func TestFieldPoint_Type(t *testing.T) { - assert.Equal(t, value.TypePoint, (&FieldPoint{p: &FieldPosition{t: value.TypePoint}}).Type()) -} - -func TestFieldPoint_TypeProperty(t *testing.T) { - f := FieldPoint{} - assert.Equal(t, &TypeProperty{ - t: f.Type(), - point: &f, - }, (&f).TypeProperty()) -} - -func TestFieldPoint_Clone(t *testing.T) { - assert.Nil(t, (*FieldPoint)(nil).Clone()) - assert.Equal(t, &FieldPoint{}, (&FieldPoint{}).Clone()) -} - -func TestFieldPoint_Validate(t *testing.T) { - assert.NoError(t, (&FieldPoint{p: &FieldPosition{t: value.TypePoint}}).Validate(value.TypePoint.Value([]float64{1.12345, 2.12345}))) - assert.Equal(t, ErrInvalidValue, (&FieldPoint{p: &FieldPosition{t: value.TypePoint}}).Validate(value.TypePoint.Value([]float64{1.12345}))) - assert.Equal(t, ErrInvalidValue, (&FieldPoint{p: &FieldPosition{t: value.TypePoint}}).Validate(value.TypePoint.Value(""))) -} diff --git a/server/pkg/schema/field_position.go b/server/pkg/schema/field_position.go deleted file mode 100644 index b6b4ff1e89..0000000000 --- a/server/pkg/schema/field_position.go +++ /dev/null @@ -1,41 +0,0 @@ -package schema - -import ( - "github.com/reearth/reearth-cms/server/pkg/value" -) - -type FieldPosition struct { - t value.Type -} - -func NewPosition(t value.Type) *FieldPosition { - return &FieldPosition{ - t: t, - } -} - -func (f *FieldPosition) Type() value.Type { - return f.t -} - -func (f *FieldPosition) Clone() *FieldPosition { - if f == nil { - return nil - } - return &FieldPosition{ - t: f.t, - } -} - -func (f *FieldPosition) Validate(v *value.Value) error { - if v.Type() != f.t { - return ErrInvalidValue - } - - vp, ok := v.ValuePosition() - if !ok || len(vp) < 2 { - return ErrInvalidValue - } - - return nil -} diff --git a/server/pkg/schema/field_position_test.go b/server/pkg/schema/field_position_test.go deleted file mode 100644 index f96a3c8d29..0000000000 --- a/server/pkg/schema/field_position_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package schema - -import ( - "testing" - - "github.com/reearth/reearth-cms/server/pkg/value" - "github.com/stretchr/testify/assert" -) - -func TestNewPosition(t *testing.T) { - assert.Equal(t, &FieldPosition{t: value.TypePoint}, NewPosition(value.TypePoint)) -} - -func TestFieldPosition_Type(t *testing.T) { - assert.Equal(t, value.TypePoint, (&FieldPosition{t: value.TypePoint}).Type()) -} - -func TestFieldPosition_Clone(t *testing.T) { - assert.Nil(t, (*FieldPosition)(nil).Clone()) - assert.Equal(t, &FieldPosition{t: value.TypePoint}, (&FieldPosition{t: value.TypePoint}).Clone()) -} - -func TestFieldPosition_Validate(t *testing.T) { - assert.NoError(t, (&FieldPosition{t: value.TypePoint}).Validate(value.TypePoint.Value([]float64{1.12345, 2.12345}))) - assert.Equal(t, ErrInvalidValue, (&FieldPosition{t: value.TypePoint}).Validate(value.TypeNumber.Value([]float64{1.12345}))) - assert.Equal(t, ErrInvalidValue, (&FieldPosition{t: value.TypePoint}).Validate(value.TypeNumber.Value(1))) -} diff --git a/server/pkg/schema/type_property.go b/server/pkg/schema/type_property.go index 32181ebd7f..86a9803509 100644 --- a/server/pkg/schema/type_property.go +++ b/server/pkg/schema/type_property.go @@ -27,7 +27,7 @@ type TypeProperty struct { reference *FieldReference url *FieldURL group *FieldGroup - point *FieldPoint + geometry *FieldGeometry } type TypePropertyMatch struct { @@ -46,7 +46,7 @@ type TypePropertyMatch struct { Reference func(*FieldReference) URL func(*FieldURL) Group func(*FieldGroup) - Point func(*FieldPoint) + Geometry func(*FieldGeometry) Default func() } @@ -66,7 +66,7 @@ type TypePropertyMatch1[T any] struct { Reference func(*FieldReference) T URL func(*FieldURL) T Group func(*FieldGroup) T - Point func(*FieldPoint) T + Geometry func(*FieldGeometry) T Default func() T } @@ -121,7 +121,7 @@ func (t *TypeProperty) Validate(v *value.Value) error { Group: func(f *FieldGroup) error { return f.Validate(v) }, - Point: func(f *FieldPoint) error { + Geometry: func(f *FieldGeometry) error { return f.Validate(v) }, }) @@ -171,7 +171,7 @@ func (t *TypeProperty) ValidateMultiple(v *value.Multiple) error { Group: func(f *FieldGroup) error { return f.ValidateMultiple(v) }, - Point: func(f *FieldPoint) error { + Geometry: func(f *FieldGeometry) error { return f.ValidateMultiple(v) }, }) @@ -261,9 +261,9 @@ func (t *TypeProperty) Match(m TypePropertyMatch) { m.URL(t.url) return } - case value.TypePoint: - if m.Point != nil { - m.Point(t.point) + case value.TypeGeometry: + if m.Geometry != nil { + m.Geometry(t.geometry) return } } @@ -295,7 +295,7 @@ func (t *TypeProperty) Clone() *TypeProperty { reference: t.reference.Clone(), group: t.group.Clone(), url: t.url.Clone(), - point: t.point.Clone(), + geometry: t.geometry.Clone(), } } @@ -368,9 +368,9 @@ func MatchTypeProperty1[T any](t *TypeProperty, m TypePropertyMatch1[T]) (res T) if m.Group != nil { return m.Group(t.group) } - case value.TypePoint: - if m.Point != nil { - return m.Point(t.point) + case value.TypeGeometry: + if m.Geometry != nil { + return m.Geometry(t.geometry) } } diff --git a/server/pkg/schema/type_property_test.go b/server/pkg/schema/type_property_test.go index bf8cb9210b..1b3a90a7f0 100644 --- a/server/pkg/schema/type_property_test.go +++ b/server/pkg/schema/type_property_test.go @@ -30,6 +30,7 @@ func TestMatchTypeProperty(t *testing.T) { Number: func(_ *FieldNumber) { val = "Number" }, Reference: func(_ *FieldReference) { val = "Reference" }, URL: func(_ *FieldURL) { val = "URL" }, + Geometry: func(_ *FieldGeometry) { val = "Geometry" }, Default: func() { val = "Default" }, } @@ -155,6 +156,14 @@ func TestMatchTypeProperty(t *testing.T) { }, want: "URL", }, + { + name: "Geometry", + args: args{ + tp: &TypeProperty{t: value.TypeGeometry, geometry: &FieldGeometry{}}, + m: m, + }, + want: "Geometry", + }, { name: "Default", args: args{ @@ -192,6 +201,7 @@ func TestMatchTypeProperty1(t *testing.T) { Number: func(_ *FieldNumber) string { return "Number" }, Reference: func(_ *FieldReference) string { return "Reference" }, URL: func(_ *FieldURL) string { return "URL" }, + Geometry: func(_ *FieldGeometry) string { return "Geometry" }, Default: func() string { return "Default" }, } @@ -317,6 +327,14 @@ func TestMatchTypeProperty1(t *testing.T) { }, want: "URL", }, + { + name: "Geometry", + args: args{ + tp: &TypeProperty{t: value.TypeGeometry, geometry: &FieldGeometry{}}, + m: m, + }, + want: "Geometry", + }, { name: "Default", args: args{ @@ -463,6 +481,17 @@ func TestTypeProperty_Validate(t *testing.T) { }, want: nil, }, + { + name: "Geometry", + args: args{ + tp: &TypeProperty{t: value.TypeGeometry, geometry: NewGeometry(GeometrySupportedTypeList{"POINT"})}, + value: value.TypeGeometry.Value(`{ + "type": "Point", + "coordinates": [102.0, 0.5] + }`), + }, + want: nil, + }, } for _, tc := range tests { diff --git a/server/pkg/value/match.go b/server/pkg/value/match.go index 530eba2832..aebf639842 100644 --- a/server/pkg/value/match.go +++ b/server/pkg/value/match.go @@ -17,6 +17,7 @@ type Match struct { Reference func(Reference) URL func(URL) Group func(Group) + Geometry func(String) Default func() } @@ -103,6 +104,11 @@ func (v *Value) Match(m Match) { m.Group(v.v.(Group)) return } + case TypeGeometry: + if m.Geometry != nil { + m.Geometry(v.v.(String)) + return + } } if m.Default != nil { diff --git a/server/pkg/value/match_test.go b/server/pkg/value/match_test.go index f37a3d8cdf..bceb294e64 100644 --- a/server/pkg/value/match_test.go +++ b/server/pkg/value/match_test.go @@ -50,6 +50,14 @@ func TestValue_Match(t *testing.T) { res = nil (&Value{t: TypeBool}).Match(Match{Default: func() { res = "default" }}) assert.Equal(t, "default", res) + + res = nil + g := `{ + "type": "Point", + "coordinates": [102.0, 0.5] + }` + (&Value{t: TypeGeometry, v: g}).Match(Match{Geometry: func(v string) { res = v }}) + assert.Equal(t, g, res) } func TestOptional_Match(t *testing.T) { diff --git a/server/pkg/value/position.go b/server/pkg/value/position.go deleted file mode 100644 index 9a91902cd9..0000000000 --- a/server/pkg/value/position.go +++ /dev/null @@ -1,201 +0,0 @@ -package value - -import ( - "encoding/json" - "slices" - "strconv" - - "github.com/samber/lo" -) - -const TypePoint Type = "point" -// const TypeLineString Type = "lineString" -// const TypePolygon Type = "polygon" - -type propertyPosition struct{} - -type Position = []float64 - -func (p *propertyPosition) ToValue(i any) (any, bool) { - if i == nil { - return nil, true - } - - switch v := i.(type) { - case []float64: - return v, true - case []float32: - return mapFloat32ToFloat64(v) - case []int: - return mapIntegersToFloat64(v), true - case []int8: - return mapIntegersToFloat64(v), true - case []int16: - return mapIntegersToFloat64(v), true - case []int32: - return mapIntegersToFloat64(v), true - case []int64: - return mapIntegersToFloat64(v), true - case []uint: - return mapIntegersToFloat64(v), true - case []uint8: - return mapIntegersToFloat64(v), true - case []uint16: - return mapIntegersToFloat64(v), true - case []uint32: - return mapIntegersToFloat64(v), true - case []uint64: - return mapIntegersToFloat64(v), true - case []uintptr: - return mapIntegersToFloat64(v), true - case []json.Number: - return mapJSONNumbersToFloat64(v) - case []string: - return mapStringsToFloat64(v) - default: - return nil, false - } -} - -func mapIntegersToFloat64[T any](v []T) []float64 { - return lo.Map(v, func(n T, _ int) float64 { - return intToFloat64(n) - }) -} - -func intToFloat64(v any) float64 { - switch val := v.(type) { - case int: - return float64(val) - case int8: - return float64(val) - case int16: - return float64(val) - case int32: - return float64(val) - case int64: - return float64(val) - case uint: - return float64(val) - case uint8: - return float64(val) - case uint16: - return float64(val) - case uint32: - return float64(val) - case uint64: - return float64(val) - case uintptr: - return float64(val) - default: - return 0 - } -} - -func mapStringsToFloat64(v []string) ([]float64, bool) { - var err error - s := lo.Map(v, func(s string, _ int) float64 { - vv, err2 := strconv.ParseFloat(s, 64) - if err2 != nil { - err = err2 - return 0 - } - return vv - }) - if err != nil { - return nil, false - } - return s, true -} - -func mapJSONNumbersToFloat64(v []json.Number) ([]float64, bool) { - var err error - s := lo.Map(v, func(n json.Number, _ int) float64 { - vv, err2 := n.Float64() - if err2 != nil { - err = err2 - return 0 - } - return vv - }) - if err != nil { - return nil, false - } - return s, true -} - -func mapFloat32ToFloat64(v []float32) ([]float64, bool) { - var err error - s := lo.Map(v, func(n float32, _ int) float64 { - ss := strconv.FormatFloat(float64(n), 'f', -1, 32) - vv, err2 := strconv.ParseFloat(ss, 64) - if err2 != nil { - err = err2 - return 0 - } - return vv - }) - if err != nil { - return nil, false - } - return s, true -} - -func (*propertyPosition) ToInterface(v any) (any, bool) { - return v, true -} - -func (*propertyPosition) Validate(i any) bool { - v, ok := i.(Position) - if !ok { - return false - } - return len(v) >= 2 -} - -func (*propertyPosition) Equal(v, w any) bool { - vv := v.(Position) - ww := w.(Position) - if len(vv) != len(ww) { - return false - } - return slices.Equal(vv, ww) -} - -func (*propertyPosition) IsEmpty(i any) bool { - if i == nil { - return true - } - v, ok := i.(Position) - if !ok { - return true - } - return len(v) == 0 -} - -func (v *Value) ValuePosition() (vv Position, ok bool) { - if v == nil { - return - } - vv, ok = v.v.(Position) - if !ok { - return nil, false - } - if len(vv) > 3 { - return vv[:3], true // TODO: need to think about his case - } - return -} - -func (m *Multiple) ValuesPosition() (vv []Position, ok bool) { - if m == nil { - return - } - vv = lo.FilterMap(m.v, func(v *Value, _ int) (Position, bool) { - return v.ValuePosition() - }) - if len(vv) != len(m.v) { - return nil, false - } - return vv, true -} diff --git a/server/pkg/value/position_test.go b/server/pkg/value/position_test.go deleted file mode 100644 index c80cb5ad73..0000000000 --- a/server/pkg/value/position_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package value - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_propertyPosition_ToValue(t *testing.T) { - tests := []struct { - name string - arg any - want1 any - want2 bool - }{ - { - name: "nil", - arg: nil, - want1: nil, - want2: true, - }, - { - name: "string", - arg: []string{"1.12345", "2.12345"}, - want1: []float64{1.12345, 2.12345}, - want2: true, - }, - { - name: "json.Number", - arg: []json.Number{"1.12345", "2.12345"}, - want1: []float64{1.12345, 2.12345}, - want2: true, - }, - { - name: "float64", - arg: []float64{1.12345, 2.12345}, - want1: []float64{1.12345, 2.12345}, - want2: true, - }, - { - name: "float32", - arg: []float32{1.1234567, 2.12345}, - want1: []float64{1.1234567, 2.12345}, - want2: true, - }, - { - name: "int", - arg: []int{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "int8", - arg: []int8{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "int16", - arg: []int16{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "int32", - arg: []int32{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "int64", - arg: []int64{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uint", - arg: []uint{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uint8", - arg: []uint8{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uint16", - arg: []uint16{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uint32", - arg: []uint32{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uint64", - arg: []uint64{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - { - name: "uintptr", - arg: []uintptr{1, 2}, - want1: []float64{1.0, 2.0}, - want2: true, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - p := &propertyPosition{} - got1, got2 := p.ToValue(tt.arg) - assert.Equal(t, tt.want1, got1) - assert.Equal(t, tt.want2, got2) - }) - } -} - -func Test_propertyPosition_ToInterface(t *testing.T) { - v := []float64{1.1, 2.1, 3.1} - tt, ok := (&propertyPosition{}).ToInterface(v) - assert.Equal(t, v, tt) - assert.Equal(t, true, ok) -} - -func Test_propertyPosition_IsEmpty(t *testing.T) { - assert.True(t, (&propertyPosition{}).IsEmpty([]float64{})) - assert.False(t, (&propertyPosition{}).IsEmpty([]float64{1.1, 2.1, 3.1})) -} - -func Test_propertyPosition_Validate(t *testing.T) { - assert.True(t, (&propertyPosition{}).Validate([]float64{1.1, 2.1, 3.1})) - assert.False(t, (&propertyPosition{}).Validate([]float64{1.1})) - assert.False(t, (&propertyPosition{}).Validate([]int{1, 2, 3})) - assert.False(t, (&propertyPosition{}).Validate([]string{"1", "2", "3"})) - assert.False(t, (&propertyPosition{}).Validate(1)) -} - -func Test_propertyPosition_Equal(t *testing.T) { - ps := &propertyPosition{} - assert.True(t, ps.Equal(Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1})) - ps1 := &propertyPosition{} - assert.False(t, ps1.Equal(Position{1.1, 2.1, 3.1}, Position{1.1, 2.1})) -} - -func TestValue_ValuePosition(t *testing.T) { - var v *Value - got, ok := v.ValuePosition() - assert.Equal(t, []float64(nil), got) - assert.Equal(t, false, ok) - - v = &Value{ - v: []float64{1.1, 2.1, 3.1}, - } - got, ok = v.ValuePosition() - assert.Equal(t, []float64{1.1, 2.1, 3.1}, got) - assert.Equal(t, true, ok) -} - -func TestMultiple_ValuesPosition(t *testing.T) { - var m *Multiple - got, ok := m.ValuesPosition() - var expected []Position - assert.Equal(t, expected, got) - assert.False(t, ok) - - m = NewMultiple(TypePoint, []any{Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1}}) - expected = []Position{{1.1, 2.1, 3.1}, {1.1, 2.1, 3.1}, {1.1, 2.1, 3.1}} - got, ok = m.ValuesPosition() - assert.Equal(t, expected, got) - assert.True(t, ok) -} diff --git a/server/pkg/value/registry.go b/server/pkg/value/registry.go index 70c31c53ab..2b21f68084 100644 --- a/server/pkg/value/registry.go +++ b/server/pkg/value/registry.go @@ -16,7 +16,7 @@ var defaultTypes = TypeRegistry{ TypeGroup: &propertyGroup{}, TypeReference: &propertyReference{}, TypeURL: &propertyURL{}, - TypePoint: &propertyPosition{}, + TypeGeometry: &propertyString{}, } type TypeRegistry map[Type]TypeProperty diff --git a/server/pkg/value/string.go b/server/pkg/value/string.go index 58f3d3e4fe..da594b401f 100644 --- a/server/pkg/value/string.go +++ b/server/pkg/value/string.go @@ -14,6 +14,7 @@ const TypeRichText Type = "richText" const TypeMarkdown Type = "markdown" const TypeSelect Type = "select" const TypeTag Type = "tag" +const TypeGeometry Type = "geometry" type propertyString struct{} diff --git a/server/schemas/field.graphql b/server/schemas/field.graphql index 630d30d779..af066991fc 100644 --- a/server/schemas/field.graphql +++ b/server/schemas/field.graphql @@ -13,7 +13,7 @@ enum SchemaFieldType { Checkbox URL Group - Point + Geometry } enum SchemaFieldTagColor { @@ -30,6 +30,16 @@ enum SchemaFieldTagColor { PURPLE } +enum GeometrySupportedType { + POINT + MULTIPOINT + LINESTRING + MULTILINESTRING + POLYGON + MULTIPOLYGON + GEOMETRYCOLLECTION +} + type SchemaField { id: ID! modelId: ID @@ -67,7 +77,7 @@ union SchemaFieldTypeProperty = | SchemaFieldURL | SchemaFieldCheckbox | SchemaFieldGroup - | SchemaFieldPoint + | SchemaFieldGeometry type SchemaFieldText { defaultValue: Any @@ -143,8 +153,9 @@ type SchemaFieldGroup { groupId: ID! } -type SchemaFieldPoint { +type SchemaFieldGeometry { defaultValue: Any + supportedTypes: [GeometrySupportedType!]! } # Inputs @@ -229,7 +240,12 @@ input SchemaFieldGroupInput { groupId: ID! } -input SchemaFieldPointInput { +input SchemaFieldGeometryInput { + defaultValue: Any + supportedTypes: [GeometrySupportedType!]! +} + +input SchemaFieldLineStringInput { defaultValue: Any } @@ -248,7 +264,7 @@ input SchemaFieldTypePropertyInput @onlyOne { reference: SchemaFieldReferenceInput url: SchemaFieldURLInput group: SchemaFieldGroupInput - point: SchemaFieldPointInput + geometry: SchemaFieldGeometryInput } input CreateFieldInput {