diff --git a/config.go b/config.go index 0d3651d..b3975be 100644 --- a/config.go +++ b/config.go @@ -1,9 +1,10 @@ package firemodel import ( + "regexp" "strings" + "github.com/pkg/errors" - "regexp" ) type Schema struct { @@ -64,8 +65,12 @@ func (options SchemaModelOptions) GetFirestorePath() (format string, args []stri err = errors.Errorf(`firemodel: invalid path option "%s"`, pathTemplate) return } - components := strings.Split(pathTemplate, "/") + if len(components)%2 != 0 { + err = errors.Errorf(`firemodel: invalid path option (must be even number of components) "%s"`, pathTemplate) + return + } + for idx, component := range components { if firestorePathConstantPattern.MatchString(component) { continue @@ -99,31 +104,49 @@ type SchemaField struct { Name string Comment string Type SchemaFieldType - Extras *SchemaFieldExtras } -type SchemaFieldType string +type SchemaFieldType interface { + isSchemaTypeName() +} type SchemaEnumValue struct { Name string Comment string } -type SchemaFieldExtras struct { - ReferenceTo string - ArrayOfPrimitive SchemaFieldType - ArrayOfModel string - ArrayOfEnum string - MapToPrimitive SchemaFieldType - MapToModel string - MapToEnum string - EnumType string - URL bool - File bool -} +type Boolean struct{} +type Integer struct{} +type Double struct{} +type GeoPoint struct{} +type Timestamp struct{} +type String struct{} +type Bytes struct{} +type Reference struct{ T *SchemaModel } +type Array struct{ T SchemaFieldType } +type Map struct{ T SchemaFieldType } +type Model struct{ T *SchemaModel } +type Enum struct{ T *SchemaEnum } +type URL struct{} +type File struct{} + +func (t *Boolean) isSchemaTypeName() {} +func (t *Integer) isSchemaTypeName() {} +func (t *Double) isSchemaTypeName() {} +func (t *GeoPoint) isSchemaTypeName() {} +func (t *Timestamp) isSchemaTypeName() {} +func (t *String) isSchemaTypeName() {} +func (t *Bytes) isSchemaTypeName() {} +func (t *Reference) isSchemaTypeName() {} +func (t *Array) isSchemaTypeName() {} +func (t *Map) isSchemaTypeName() {} +func (t *Model) isSchemaTypeName() {} +func (t *Enum) isSchemaTypeName() {} +func (t *URL) isSchemaTypeName() {} +func (t *File) isSchemaTypeName() {} type SchemaNestedCollection struct { Name string Comment string - Type string + Type *Model } diff --git a/firemodel.example.firemodel b/firemodel.example.firemodel index d73993e..7667756 100644 --- a/firemodel.example.firemodel +++ b/firemodel.example.firemodel @@ -9,7 +9,7 @@ enum TestDirection { // A Test is a test model. model TestModel { - option firestore.path = "users/{user_id}/test_models"; + option firestore.path = "users/{user_id}/test_models/{test_model_id}"; option firestore.autotimestamp = true; // The name. @@ -29,6 +29,7 @@ model TestModel { array directions; array models; array refs; + array> model_refs; map meta; map meta_strs; TestDirection direction; @@ -39,5 +40,6 @@ model TestModel { } model TestTimestamps { + option firestore.path = "timestamps/{test_timestamps_id}"; option firestore.autotimestamp = true; } diff --git a/firemodel.example.yml b/firemodel.example.yml deleted file mode 100644 index e65b268..0000000 --- a/firemodel.example.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- -options: - ts: - namespace: "example" -enums: -- name: Direction - values: - - name: left - - name: right - - name: up - - name: down -models: -- name: TestModel - comment: A Test is a test model. - options: - firestore: - path: "users/{user_id}/test_models" - fields: - - comment: The name. - name: name - type: string - - name: age - comment: The age. - type: integer - - name: pi - comment: The number pi. - type: double - - name: birthdate - comment: The birth date. - type: timestamp - - name: is_good - type: boolean - comment: True if it is good. - - name: data - type: bytes - - name: friend - type: reference - extras: - referenceTo: TestModel - - name: location - type: geopoint - - name: colors - type: array - extras: - arrayOfPrimitive: string - - name: meta - type: map - - name: direction - type: map - extras: - mapToEnum: Direction - - name: test_file - type: map - extras: - file: true - - name: url - type: string - extras: - url: true - collections: - - name: nested - type: TestModel diff --git a/firemodel.go b/firemodel.go index 53c6157..a4a7c91 100644 --- a/firemodel.go +++ b/firemodel.go @@ -1,22 +1,9 @@ package firemodel import ( - "github.com/mickeyreiss/firemodel/internal/ast" "io" - "github.com/go-errors/errors" -) -const ( - Boolean = SchemaFieldType(ast.Boolean) - Integer = SchemaFieldType(ast.Integer) - Double = SchemaFieldType(ast.Double) - Timestamp = SchemaFieldType(ast.Timestamp) - String = SchemaFieldType(ast.String) - Bytes = SchemaFieldType(ast.Bytes) - Reference = SchemaFieldType(ast.Reference) - GeoPoint = SchemaFieldType(ast.GeoPoint) - Array = SchemaFieldType(ast.Array) - Map = SchemaFieldType(ast.Map) + "github.com/go-errors/errors" ) type SourceCoder interface { diff --git a/firemodel_test.go b/firemodel_test.go index f69ba10..3981254 100644 --- a/firemodel_test.go +++ b/firemodel_test.go @@ -61,7 +61,6 @@ type testCtx struct { } func runTest(t *testing.T, schema *firemodel.Schema) { - t.Helper() ctx := testCtx{ prefix: path.Join(fixturesRoot, t.Name()), files: make(inMemoryFilesByName), diff --git a/internal/ast/parser.go b/internal/ast/parser.go index 9f241fa..5133ccc 100644 --- a/internal/ast/parser.go +++ b/internal/ast/parser.go @@ -1,6 +1,7 @@ package ast import ( + "fmt" "io" "regexp" "sort" @@ -127,8 +128,36 @@ type ASTField struct { } type ASTFieldType struct { - Base ASTType `parser:"@Ident"` - Generic ASTType `parser:"[ '<' @Ident '>' ]"` + Base ASTType `parser:"@Ident"` + Generic *ASTFieldType `parser:"[ '<' @@ '>' ]"` +} + +func (ft *ASTFieldType) String() string { + if ft.Generic != nil { + return fmt.Sprintf("%s<%s>", ft.Base, ft.Generic) + } else { + return fmt.Sprintf("%s", ft.Base) + } +} + +func (ft *ASTFieldType) IsPrimitive() bool { + switch ft.Base { + case String, + Integer, + Bytes, + Double, + Timestamp, + Boolean, + Reference, + GeoPoint, + Array, + Map: + return true + case collection: + panic("firemodel/schema: bug. collection should never be treated as primitive type.") + default: + return false + } } type ASTType string @@ -154,23 +183,3 @@ const ( func (s ASTType) IsCollection() bool { return s == collection } - -func (s ASTType) IsPrimitive() bool { - switch s { - case String, - Integer, - Bytes, - Double, - Timestamp, - Boolean, - Reference, - GeoPoint, - Array, - Map: - return true - case collection: - panic("firemodel/schema: bug. collection should never be treated as primitive type.") - default: - return false - } -} diff --git a/langs/go/go.go b/langs/go/go.go index 05f87ed..e163bb4 100644 --- a/langs/go/go.go +++ b/langs/go/go.go @@ -159,7 +159,7 @@ func (m *GoModeler) fields(model *firemodel.SchemaModel) func(g *jen.Group) { } g. Id(strcase.ToCamel(field.Name)). - Do(m.goType(field.Type, field.Extras)). + Do(m.goType(field.Type)). Tag(map[string]string{"firestore": strcase.ToLowerCamel(field.Name)}) } @@ -180,53 +180,41 @@ func (m *GoModeler) fields(model *firemodel.SchemaModel) func(g *jen.Group) { } } -func (m *GoModeler) goType(firetype firemodel.SchemaFieldType, extras *firemodel.SchemaFieldExtras) func(s *jen.Statement) { - switch firetype { - case firemodel.Boolean: +func (m *GoModeler) goType(firetype firemodel.SchemaFieldType) func(s *jen.Statement) { + switch firetype := firetype.(type) { + case *firemodel.Boolean: return func(s *jen.Statement) { s.Bool() } - case firemodel.Integer: + case *firemodel.Integer: return func(s *jen.Statement) { s.Int64() } - case firemodel.Double: + case *firemodel.Double: return func(s *jen.Statement) { s.Float64() } - case firemodel.Timestamp: + case *firemodel.Timestamp: return func(s *jen.Statement) { s.Qual("time", "Time") } - case firemodel.String: - if extras != nil && extras.EnumType != "" { - return func(s *jen.Statement) { s.Id(extras.EnumType) } - } - if extras != nil && extras.URL { - return func(s *jen.Statement) { s.Qual("github.com/mickeyreiss/firemodel/runtime", "URL") } - } + case *firemodel.String: return func(s *jen.Statement) { s.String() } - case firemodel.Bytes: + case *firemodel.URL: + return func(s *jen.Statement) { s.Qual("github.com/mickeyreiss/firemodel/runtime", "URL") } + case *firemodel.Enum: + return func(s *jen.Statement) { s.Id(firetype.T.Name) } + case *firemodel.Bytes: return func(s *jen.Statement) { s.Index().Byte() } - case firemodel.Reference: + case *firemodel.Reference: return func(s *jen.Statement) { s.Op("*").Qual("cloud.google.com/go/firestore", "DocumentRef") } - case firemodel.GeoPoint: + case *firemodel.GeoPoint: return func(s *jen.Statement) { s.Op("*").Qual("google.golang.org/genproto/googleapis/type/latlng", "LatLng") } - case firemodel.Array: - if extras != nil && extras.ArrayOfModel != "" { - return func(s *jen.Statement) { s.Index().Id(extras.ArrayOfModel) } - } - if extras != nil && extras.ArrayOfEnum != "" { - return func(s *jen.Statement) { s.Index().Id(extras.ArrayOfEnum) } - } - if extras != nil && extras.ArrayOfPrimitive != "" { - return func(s *jen.Statement) { s.Index().Do(m.goType(extras.ArrayOfPrimitive, nil)) } + case *firemodel.Model: + return func(s *jen.Statement) { s.Op("*").Id(firetype.T.Name) } + case *firemodel.Array: + if firetype.T != nil { + return func(s *jen.Statement) { s.Index().Do(m.goType(firetype.T)) } } return func(s *jen.Statement) { s.Index().Interface() } - case firemodel.Map: - if extras != nil && extras.File { - return func(s *jen.Statement) { s.Op("*").Qual("github.com/mickeyreiss/firemodel/runtime", "File") } - } - if extras != nil && extras.MapToModel != "" { - return func(s *jen.Statement) { s.Op("*").Id(extras.MapToModel) } - } else if extras != nil && extras.MapToEnum != "" { - return func(s *jen.Statement) { s.Op("*").Id(extras.MapToEnum) } - } else if extras != nil && extras.MapToPrimitive != "" { - return func(s *jen.Statement) { s.Map(jen.String()).Do(m.goType(extras.MapToPrimitive, nil)) } + case *firemodel.File: + return func(s *jen.Statement) { s.Op("*").Qual("github.com/mickeyreiss/firemodel/runtime", "File") } + case *firemodel.Map: + if firetype.T != nil { + return func(s *jen.Statement) { s.Map(jen.String()).Do(m.goType(firetype.T)) } } - return func(s *jen.Statement) { s.Map(jen.String()).Interface() } default: err := errors.Errorf("firemodel/go: unknown type %s", firetype) diff --git a/langs/ios/ios.go b/langs/ios/ios.go index 8a89d37..c5eb5a5 100644 --- a/langs/ios/ios.go +++ b/langs/ios/ios.go @@ -2,6 +2,7 @@ package ios import ( "fmt" + "log" "strings" "text/template" @@ -34,13 +35,13 @@ var ( tpl = template.Must(template. New("file"). Funcs(map[string]interface{}{ - "firemodelVersion": func() string { return version.Version }, - "toSwiftType": toSwiftType, - "toScreamingSnake": strcase.ToScreamingSnake, - "toCamel": strcase.ToCamel, - "toLowerCamel": strcase.ToLowerCamel, - "filterFieldsEnumsOnly": filterFieldsEnumsOnly, - "asStaticConfigForFirestorePath": asStaticConfigForFirestorePath, + "firemodelVersion": func() string { return version.Version }, + "toSwiftType": toSwiftType, + "toScreamingSnake": strcase.ToScreamingSnake, + "toCamel": strcase.ToCamel, + "toLowerCamel": strcase.ToLowerCamel, + "filterFieldsEnumsOnly": filterFieldsEnumsOnly, + "firestorePath": firestorePath, }). Parse(file), ) @@ -51,7 +52,7 @@ var ( func filterFieldsEnumsOnly(in []*firemodel.SchemaField) []*firemodel.SchemaField { var out []*firemodel.SchemaField for _, i := range in { - if i.Extras == nil || i.Extras.EnumType == "" { + if _, ok := i.Type.(*firemodel.Enum); !ok { continue } out = append(out, i) @@ -59,85 +60,76 @@ func filterFieldsEnumsOnly(in []*firemodel.SchemaField) []*firemodel.SchemaField return out } -func toSwiftType(extras *firemodel.SchemaFieldExtras, root bool, firetype firemodel.SchemaFieldType) string { - switch firetype { - case firemodel.Boolean: +func toSwiftType(root bool, firetype firemodel.SchemaFieldType) string { + switch firetype := firetype.(type) { + case *firemodel.Boolean: return "Bool = false" - case firemodel.Integer: + case *firemodel.Integer: return "Int = 0" - case firemodel.Double: + case *firemodel.Double: return "Float = 0.0" - case firemodel.Timestamp: + case *firemodel.Timestamp: return "Date = Date()" - case firemodel.String: - if extras != nil && extras.EnumType != "" { - if root { - return fmt.Sprintf("%s?", strcase.ToCamel(extras.EnumType)) - } else { - return fmt.Sprintf("%s", strcase.ToCamel(extras.EnumType)) - } - } else if extras != nil && extras.URL { - if root { - return "URL?" - } else { - return "URL" - } + case *firemodel.URL: + if root { + return "URL?" } else { - if root { - return "String?" - } else { - return "String" - } + return "URL" } - case firemodel.Bytes: + case *firemodel.String: + if root { + return "String?" + } else { + return "String" + } + case *firemodel.Bytes: if root { return "Data?" } else { return "Data" } - case firemodel.Reference: - if extras != nil && extras.ReferenceTo != "" { - return fmt.Sprintf("Pring.Reference<%s> = .init()", extras.ReferenceTo) + case *firemodel.Reference: + if firetype.T != nil { + if root { + return fmt.Sprintf("Pring.Reference<%s> = .init()", strcase.ToCamel(firetype.T.Name)) + } else { + return fmt.Sprintf("Pring.Reference<%s>", strcase.ToCamel(firetype.T.Name)) + } } else { return "Pring.AnyReference" } - case firemodel.GeoPoint: + case *firemodel.GeoPoint: if root { return "Pring.GeoPoint?" } else { return "Pring.GeoPoint" } - case firemodel.Array: - if extras != nil && extras.ArrayOfModel != "" { - return fmt.Sprintf("[%s] = []", extras.ArrayOfModel) - } else if extras != nil && extras.ArrayOfEnum != "" { - return fmt.Sprintf("[%s] = []", extras.ArrayOfEnum) - } else if extras != nil && extras.ArrayOfPrimitive != "" { - return fmt.Sprintf("[%s] = []", toSwiftType(nil, false, extras.ArrayOfPrimitive)) + case *firemodel.Array: + if firetype.T != nil { + return fmt.Sprintf("[%s]?", toSwiftType(false, firetype.T)) + } + return "[Any]" + case *firemodel.File: + if root { + return "Pring.File?" } else { - return "[Any]" + return "Pring.File" } - case firemodel.Map: - if extras != nil && extras.File { - if root { - return "Pring.File?" - } else { - return "Pring.File" - } - } else if extras != nil && extras.MapToModel != "" { - if root { - return fmt.Sprintf("%s?", extras.MapToModel) - } else { - return fmt.Sprintf("%s", extras.MapToModel) - } - } else if extras != nil && extras.MapToEnum != "" { - if root { - return fmt.Sprintf("%s?", extras.MapToEnum) - } else { - return fmt.Sprintf("%s", extras.MapToEnum) - } - } else if extras != nil && extras.MapToPrimitive != "" { - return fmt.Sprintf("[String: %s] = [:]", toSwiftType(nil, false, extras.MapToPrimitive)) + case *firemodel.Model: + if root { + return fmt.Sprintf("%s?", firetype.T.Name) + } else { + return fmt.Sprintf("%s", firetype.T.Name) + } + case *firemodel.Enum: + if root { + return fmt.Sprintf("%s?", strcase.ToCamel(firetype.T.Name)) + } else { + return fmt.Sprintf("%s", strcase.ToCamel(firetype.T.Name)) + } + case *firemodel.Map: + if firetype.T != nil { + return fmt.Sprintf("[String: %s] = [:]", toSwiftType(false, firetype.T)) } else { return "[AnyHashable: Any] = [:]" } @@ -147,11 +139,17 @@ func toSwiftType(extras *firemodel.SchemaFieldExtras, root bool, firetype firemo } } -func asStaticConfigForFirestorePath(options firemodel.SchemaModelOptions) string { - format, args, err := options.GetFirestorePath() +func firestorePath(model firemodel.SchemaModel) string { + format, args, err := model.Options.GetFirestorePath() if err != nil { panic(err) } + + if len(args) == 0 { + log.Printf("ios: warning: no firestore path for %s", model.Name) + return "" + } + var out strings.Builder for _, arg := range args { @@ -164,7 +162,7 @@ func asStaticConfigForFirestorePath(options firemodel.SchemaModelOptions) string } path := fmt.Sprintf(format, argsWrappedInInterpolationParens...) - fmt.Fprintf(&out, " override class var path: String { return \"%s\" }\n", path) + fmt.Fprintf(&out, " override class var path: String { return \"%s\" }\n", path) return out.String() } @@ -188,26 +186,25 @@ import Pring // TODO: Add documentation to {{.Name | toCamel}}. {{- end}} @objcMembers class {{.Name | toCamel}}: Pring.Object { - {{.Options | asStaticConfigForFirestorePath -}} + {{- . | firestorePath -}} {{- range .Fields}} {{- if .Comment}} // {{.Comment}} {{- else }} // TODO: Add documentation to {{.Name | toLowerCamel}}. {{- end}} - dynamic var {{.Name | toLowerCamel -}}: {{.Type | toSwiftType .Extras true}} + dynamic var {{.Name | toLowerCamel -}}: {{.Type | toSwiftType true}} {{- end}} - {{- range .Collections}} {{- if .Comment}} // {{.Comment}} {{- else }} // TODO: Add documentation to {{.Name}}. {{- end}} - dynamic var {{.Name | toLowerCamel}}: Pring.NestedCollection<{{.Type}}> = [] + dynamic var {{.Name | toLowerCamel}}: Pring.NestedCollection<{{.Type.T.Name}}> = [] {{- end}} + {{- if .Fields | filterFieldsEnumsOnly}} - {{ if .Fields | filterFieldsEnumsOnly -}} override func encode(_ key: String, value: Any?) -> Any? { switch key { {{range .Fields | filterFieldsEnumsOnly -}} @@ -247,7 +244,7 @@ import Pring {{- else}} // TODO: Add documentation to {{.Name}}. {{- end}} - case {{.Name}} + case {{.Name | toLowerCamel}} {{- end}} } @@ -272,8 +269,6 @@ extension {{.Name}}: CustomDebugStringConvertible { case .{{.Name | toLowerCamel}}: return "{{.Name | toScreamingSnake}}" {{- end}} - default: - return nil } } diff --git a/langs/ts/ts.go b/langs/ts/ts.go index 5eadc0f..3c9ea46 100644 --- a/langs/ts/ts.go +++ b/langs/ts/ts.go @@ -52,51 +52,43 @@ func interfaceName(sym string) string { return fmt.Sprintf("I%s", sym) } -func toTypescriptType(firetype firemodel.SchemaFieldType, extras *firemodel.SchemaFieldExtras) string { - switch firetype { - case firemodel.Boolean: +func toTypescriptType(firetype firemodel.SchemaFieldType) string { + switch firetype := firetype.(type) { + case *firemodel.Boolean: return "boolean" - case firemodel.Integer, firemodel.Double: + case *firemodel.Integer, *firemodel.Double: return "number" - case firemodel.Timestamp: + case *firemodel.Timestamp: return "firestore.Timestamp" - case firemodel.String: - if extras != nil && extras.EnumType != "" { - return extras.EnumType - } else if extras != nil && extras.URL { - return "URL" - } else { - return "string" - } - case firemodel.Bytes: + case *firemodel.String: + return "string" + case *firemodel.Enum: + return firetype.T.Name + case *firemodel.URL: + return "URL" + case *firemodel.Bytes: return "firestore.Blob" - case firemodel.Reference: - if extras != nil && extras.ReferenceTo != "" { - return fmt.Sprintf("DocumentReference<%s>", interfaceName(extras.ReferenceTo)) + case *firemodel.Reference: + if firetype.T != nil { + return fmt.Sprintf("DocumentReference<%s>", interfaceName(firetype.T.Name)) } else { return "firestore.DocumentReference" } - case firemodel.GeoPoint: + case *firemodel.GeoPoint: return "firestore.GeoPoint" - case firemodel.Array: - if extras != nil && extras.ArrayOfModel != "" { - return fmt.Sprintf("%s[]", interfaceName(extras.ArrayOfModel)) - } else if extras != nil && extras.ArrayOfEnum != "" { - return fmt.Sprintf("%s[]", extras.ArrayOfEnum) - } else if extras != nil && extras.ArrayOfPrimitive != "" { - return fmt.Sprintf("%s[]", toTypescriptType(extras.ArrayOfPrimitive, nil)) + case *firemodel.Array: + if firetype.T != nil { + return fmt.Sprintf("%s[]", toTypescriptType(firetype.T)) } else { return "any[]" } - case firemodel.Map: - if extras != nil && extras.File { - return "IFile" - } else if extras != nil && extras.MapToModel != "" { - return interfaceName(extras.MapToModel) - } else if extras != nil && extras.MapToEnum != "" { - return extras.MapToEnum - } else if extras != nil && extras.MapToPrimitive != "" { - return fmt.Sprintf("{ [key: string]: %s; }", toTypescriptType(extras.MapToPrimitive, nil)) + case *firemodel.Model: + return interfaceName(firetype.T.Name) + case *firemodel.File: + return "IFile" + case *firemodel.Map: + if firetype.T != nil { + return fmt.Sprintf("{ [key: string]: %s; }", toTypescriptType(firetype.T)) } else { return `{ [key: string]: any; }` } @@ -226,7 +218,7 @@ export namespace {{.Options | getSchemaOption "ts" "namespace" "firemodel"}} { {{- else }} /** TODO: Add documentation to {{.Name}}. */ {{- end}} - {{.Name | ToLowerCamel}}: CollectionReference<{{.Type | interfaceName | ToCamel}}>; + {{.Name | ToLowerCamel}}: CollectionReference<{{.Type.T.Name | interfaceName | ToCamel}}>; {{- end}} {{- range .Fields}} @@ -235,7 +227,7 @@ export namespace {{.Options | getSchemaOption "ts" "namespace" "firemodel"}} { {{- else }} /** TODO: Add documentation to {{.Name}}. */ {{- end}} - {{.Name | ToLowerCamel -}}?: {{toTypescriptType .Type .Extras}}; + {{.Name | ToLowerCamel -}}?: {{toTypescriptType .Type}}; {{- end}} {{- if .Options | getModelOption "firestore" "autotimestamp" false}} diff --git a/schema.go b/schema.go index 64a35d5..99d8518 100644 --- a/schema.go +++ b/schema.go @@ -160,7 +160,6 @@ func (c *configSchemaCompiler) compileFields(elements []*ast.ASTModelElement) (o Name: strcase.ToSnake(field.Name), Comment: field.Comment, Type: c.compileFieldType(field.Type), - Extras: c.compileExtras(field.Type), }) } return @@ -190,120 +189,103 @@ func (c *configSchemaCompiler) compileCollections(elements []*ast.ASTModelElemen } func (c *configSchemaCompiler) compileFieldType(astFieldType *ast.ASTFieldType) SchemaFieldType { - if astFieldType.Base.IsPrimitive() { - return SchemaFieldType(astFieldType.Base) - } if c.enums == nil { panic("bug: enum types not yet registered") } - if astFieldType.Base == ast.File { - return Map - } - if astFieldType.Base == ast.URL { - return String + if enum, ok := c.assertEnumType(astFieldType); ok { + return enum } - _, ok := c.assertEnumType(astFieldType.Base) - if ok { - return String + if model, ok := c.assertModelType(astFieldType); ok { + return model } - _, ok = c.assertModelType(astFieldType.Base) - if ok { - return Map + switch astFieldType.Base { + case ast.Boolean: + return &Boolean{} + case ast.Integer: + return &Integer{} + case ast.Double: + return &Double{} + case ast.Timestamp: + return &Timestamp{} + case ast.String: + return &String{} + case ast.Bytes: + return &Bytes{} + case ast.GeoPoint: + return &GeoPoint{} + case ast.File: + return &File{} + case ast.URL: + return &URL{} + case ast.Map: + if generic := astFieldType.Generic; generic != nil { + return &Map{T: c.compileFieldType(astFieldType.Generic)} + } + return &Map{} + case ast.Array: + if generic := astFieldType.Generic; generic != nil { + return &Array{T: c.compileFieldType(generic)} + } + return &Array{} + case ast.Reference: + if astFieldType.Generic == nil { + return &Reference{} + } else if modelType, ok := c.assertModelType(astFieldType.Generic); ok { + return &Reference{T: modelType.T} + } else { + err := errors.Errorf("firemodel: invalid generic type %s in %s<%s> (must be a model type)", astFieldType.Generic, astFieldType.Base, astFieldType.Generic) + panic(err) + } } + err := errors.Errorf("invalid type: %s", astFieldType.Base) panic(err) } -func (c *configSchemaCompiler) compileModelType(astType *ast.ASTFieldType) string { - if astType.Generic != "" { +func (c *configSchemaCompiler) compileModelType(astType *ast.ASTFieldType) *Model { + if astType.Generic != nil { err := errors.Errorf("models cannot have generics: %s<%s>", astType.Base, astType.Generic) panic(err) } - if modelType, ok := c.assertModelType(astType.Base); ok { + if modelType, ok := c.assertModelType(astType); ok { return modelType } - err := errors.Errorf("invalid type: %s", astType.Base) + err := errors.Errorf("invalid type: %s", astType) panic(err) } -func (c *configSchemaCompiler) assertModelType(astType ast.ASTType) (string, bool) { +func (c *configSchemaCompiler) assertModelType(astFieldType *ast.ASTFieldType) (*Model, bool) { if c.models == nil { panic("bug: model types not yet registered") } + if astFieldType == nil { + return nil, false + } + astType := astFieldType.Base for _, model := range c.models { if model.Name == strcase.ToCamel(string(astType)) { - return model.Name, true + return &Model{T: model}, true } } - return "", false + return nil, false } -func (c *configSchemaCompiler) assertEnumType(astType ast.ASTType) (string, bool) { - for _, enum := range c.enums { - if enum.Name == strcase.ToCamel(string(astType)) { - return enum.Name, true - } - +func (c *configSchemaCompiler) assertEnumType(astType *ast.ASTFieldType) (*Enum, bool) { + if astType == nil { + return nil, false } - return "", false -} - -func (c *configSchemaCompiler) compileExtras(astType *ast.ASTFieldType) *SchemaFieldExtras { - out := &SchemaFieldExtras{} - - if enumType, ok := c.assertEnumType(astType.Base); ok { - out.EnumType = string(enumType) - } - if modelType, ok := c.assertModelType(astType.Base); ok { - out.MapToModel = string(modelType) - } - - switch astType.Base { - case ast.URL: - out.URL = true - case ast.File: - out.File = true - } - - if astType.Generic != "" { - switch astType.Base { - case ast.Map: - if astType.Generic.IsPrimitive() { - out.MapToPrimitive = SchemaFieldType(astType.Generic) - } else if modelType, ok := c.assertModelType(astType.Generic); ok { - out.MapToModel = modelType - } else if enumType, ok := c.assertEnumType(astType.Generic); ok { - out.MapToEnum = enumType - } else { - err := errors.Errorf("firemodel/schema: unrecognized generic type: %s", astType.Generic) - panic(err) - } - case ast.Array: - if astType.Generic.IsPrimitive() { - out.ArrayOfPrimitive = SchemaFieldType(astType.Generic) - } else if modelType, ok := c.assertModelType(astType.Generic); ok { - out.ArrayOfModel = modelType - } else if enumType, ok := c.assertEnumType(astType.Generic); ok { - out.ArrayOfEnum = enumType - } else { - err := errors.Errorf("firemodel/schema: unrecognized generic type: %s", astType.Generic) - panic(err) - } - case ast.Reference: - if astType.Generic.IsPrimitive() { - err := errors.Errorf("firemodel: invalid generic type %s in %s<%s> (must be a model type)", astType.Generic, astType.Base, astType.Generic) - panic(err) - } else { - out.ReferenceTo = string(astType.Generic) + for _, enum := range c.enums { + if enum.Name == strcase.ToCamel(string(astType.Base)) { + if astType.Generic != nil { + panic(errors.Errorf("generic enums are not supported: %s %v", astType.Base, astType.Generic)) } - default: - err := errors.Errorf("firemodel: invalid generic type on %s", astType.Base) - panic(err) + return &Enum{T: enum}, true } + } - return out + return nil, false } func (c *configSchemaCompiler) compileModelOptions(elements []*ast.ASTModelElement) SchemaModelOptions { diff --git a/schema_test.go b/schema_test.go index e63039b..da7f8bd 100644 --- a/schema_test.go +++ b/schema_test.go @@ -42,9 +42,8 @@ func TestParseSchema(t *testing.T) { Name: "SimpleModel", Fields: []*SchemaField{ { - Name: "foo", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "foo", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -61,69 +60,59 @@ func TestParseSchema(t *testing.T) { Name: "TestModel", Comment: "A Test is a test model.", Fields: []*SchemaField{ - {Name: "name", + { + Name: "name", Comment: "The name.", - Type: String, - Extras: &SchemaFieldExtras{}, + Type: &String{}, }, { Name: "age", Comment: "The age.", - Type: Integer, - Extras: &SchemaFieldExtras{}, + Type: &Integer{}, }, { Name: "pi", Comment: "The number pi.", - Type: Double, - Extras: &SchemaFieldExtras{}, + Type: &Double{}, }, { Name: "birthdate", Comment: "The birth date.", - Type: Timestamp, - Extras: &SchemaFieldExtras{}, + Type: &Timestamp{}, }, { Name: "is_good", Comment: "True if it is good.", - Type: Boolean, - Extras: &SchemaFieldExtras{}, + Type: &Boolean{}, }, { - Name: "data", Type: Bytes, - Extras: &SchemaFieldExtras{}, + Name: "data", + Type: &Bytes{}, }, { - Name: "friend", - Type: Reference, - Extras: &SchemaFieldExtras{}, + Name: "friend", + Type: &Reference{T: &SchemaModel{Name: "Friend"}}, }, { - Name: "location", - Type: GeoPoint, - Extras: &SchemaFieldExtras{}, + Name: "location", + Type: &GeoPoint{}, }, { - Name: "colors", - Type: Array, - Extras: &SchemaFieldExtras{}, + Name: "colors", + Type: &Array{T: &String{}}, }, { - Name: "meta", - Type: Map, - Extras: &SchemaFieldExtras{}, + Name: "meta", + Type: &Map{}, }, { Name: "a_file", Comment: "Fake types...", - Type: Map, - Extras: &SchemaFieldExtras{File: true}, + Type: &File{}, }, { - Name: "an_url", - Type: String, - Extras: &SchemaFieldExtras{URL: true}, + Name: "an_url", + Type: &URL{}, }, }, Options: SchemaModelOptions{}, @@ -143,53 +132,52 @@ func TestParseSchema(t *testing.T) { Name: "TestModel", Fields: []*SchemaField{ { - Name: "other", - Type: Reference, - Extras: &SchemaFieldExtras{ReferenceTo: "TestModel"}}, + Name: "other", + Type: &Reference{T: &SchemaModel{Name: "TestModel"}}, + }, + { + Name: "unspecified_other", + Type: &Reference{}, + }, + { + Name: "primative_ary", + Type: &Array{T: &String{}}, + }, { - Name: "unspecified_other", - Type: Reference, - Extras: &SchemaFieldExtras{}, + Name: "model_ary", + Type: &Array{T: &Model{ /*TestModel*/ }}, }, { - Name: "primative_ary", - Type: Array, - Extras: &SchemaFieldExtras{ArrayOfPrimitive: String}, + Name: "enum_ary", + Type: &Array{T: &Enum{ /*TestModel*/ }}, }, { - Name: "model_ary", - Type: Array, - Extras: &SchemaFieldExtras{ArrayOfModel: "TestModel"}, + Name: "reference_ary", + Type: &Array{T: &Reference{ /* TestModel*/ }}, }, { - Name: "enum_ary", - Type: Array, - Extras: &SchemaFieldExtras{ArrayOfEnum: "TestEnum"}, + Name: "nested_ary", + Type: &Array{T: &Array{&String{}}}, }, { - Name: "generic_ary", - Type: Array, - Extras: &SchemaFieldExtras{}, + Name: "generic_ary", + Type: &Array{}, }, { - Name: "primative_map", - Type: Map, - Extras: &SchemaFieldExtras{MapToPrimitive: String}, + Name: "primative_map", + Type: &Map{T: &String{}}, }, { - Name: "model_map", - Type: Map, - Extras: &SchemaFieldExtras{MapToModel: "TestModel"}, + Name: "model_map", + Type: &Map{T: &Model{ /*TestModel*/ }}, }, { - Name: "enum_map", - Type: Map, - Extras: &SchemaFieldExtras{MapToEnum: "TestEnum"}, + Name: "enum_map", + Type: &Map{T: &Enum{ /*TestModel*/ }}, }, { - Name: "generic_map", - Type: Map, - Extras: &SchemaFieldExtras{}, + Name: "generic_map", + Type: &Map{}, }, }, Options: SchemaModelOptions{}, @@ -206,9 +194,8 @@ func TestParseSchema(t *testing.T) { Name: "TestModel", Fields: []*SchemaField{ { - Name: "url", - Type: String, - Extras: &SchemaFieldExtras{URL: true}, + Name: "url", + Type: &URL{}, }, }, Options: SchemaModelOptions{}, @@ -248,8 +235,7 @@ func TestParseSchema(t *testing.T) { { Comment: "The direction.", Name: "dir", - Type: String, - Extras: &SchemaFieldExtras{EnumType: "Direction"}, + Type: &Enum{ /*Direction*/ }, }, }, Options: SchemaModelOptions{}, @@ -270,9 +256,8 @@ func TestParseSchema(t *testing.T) { Name: "Operator", Fields: []*SchemaField{ { - Name: "operator_name", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "operator_name", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -281,9 +266,8 @@ func TestParseSchema(t *testing.T) { Name: "Component", Fields: []*SchemaField{ { - Name: "component_name", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "component_name", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -293,25 +277,19 @@ func TestParseSchema(t *testing.T) { Fields: []*SchemaField{ { Name: "owner", - Type: Reference, - Extras: &SchemaFieldExtras{ - ReferenceTo: "Operator", - }, + Type: &Reference{ /*Operator*/ }, }, // note: no components "field" here. { Name: "embedded_component", - Type: Map, - Extras: &SchemaFieldExtras{ - MapToModel: "Component", - }, + Type: &Model{T: &SchemaModel{Name: "Component"}}, }, }, Options: SchemaModelOptions{}, Collections: []*SchemaNestedCollection{ { Name: "components", - Type: "Component", + Type: &Model{ /* "Component"*/ }, }, }, }, @@ -360,9 +338,8 @@ func TestParseSchema(t *testing.T) { Name: "NormalCase", Fields: []*SchemaField{ { - Name: "foo_bar", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "foo_bar", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -371,9 +348,8 @@ func TestParseSchema(t *testing.T) { Name: "CamelCase", Fields: []*SchemaField{ { - Name: "foo_bar", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "foo_bar", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -382,9 +358,8 @@ func TestParseSchema(t *testing.T) { Name: "TitleCase", Fields: []*SchemaField{ { - Name: "foo_bar", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "foo_bar", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -393,9 +368,8 @@ func TestParseSchema(t *testing.T) { Name: "SnakeCase", Fields: []*SchemaField{ { - Name: "foo_bar", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "foo_bar", + Type: &String{}, }, }, Options: SchemaModelOptions{}, @@ -421,9 +395,8 @@ func TestParseSchema(t *testing.T) { Comment: "Regression test.", Fields: []*SchemaField{ { - Name: "name", - Type: String, - Extras: &SchemaFieldExtras{}, + Name: "name", + Type: &String{}, }, }, Options: SchemaModelOptions{}, diff --git a/testfixtures/firemodel/TestFiremodelFromSchema/go/test_model.firemodel.go b/testfixtures/firemodel/TestFiremodelFromSchema/go/test_model.firemodel.go index 82dfc18..05307ef 100644 --- a/testfixtures/firemodel/TestFiremodelFromSchema/go/test_model.firemodel.go +++ b/testfixtures/firemodel/TestFiremodelFromSchema/go/test_model.firemodel.go @@ -12,7 +12,7 @@ import ( // A Test is a test model. // -// Firestore document location: /users/{user_id}/test_models +// Firestore document location: /users/{user_id}/test_models/{test_model_id} type TestModel struct { // The name. Name string `firestore:"name"` @@ -35,9 +35,11 @@ type TestModel struct { // TODO: Add comment to TestModel.directions. Directions []TestDirection `firestore:"directions"` // TODO: Add comment to TestModel.models. - Models []TestModel `firestore:"models"` + Models []*TestModel `firestore:"models"` // TODO: Add comment to TestModel.refs. Refs []*firestore.DocumentRef `firestore:"refs"` + // TODO: Add comment to TestModel.model_refs. + ModelRefs []*firestore.DocumentRef `firestore:"modelRefs"` // TODO: Add comment to TestModel.meta. Meta map[string]interface{} `firestore:"meta"` // TODO: Add comment to TestModel.meta_strs. @@ -58,6 +60,6 @@ type TestModel struct { } // TestModelPath returns the path to a particular TestModel in Firestore. -func TestModelPath(userId string) string { - return fmt.Sprintf("users/%s/test_models", userId) +func TestModelPath(userId string, testModelId string) string { + return fmt.Sprintf("users/%s/test_models/%s", userId, testModelId) } diff --git a/testfixtures/firemodel/TestFiremodelFromSchema/go/test_timestamps.firemodel.go b/testfixtures/firemodel/TestFiremodelFromSchema/go/test_timestamps.firemodel.go index 002a9ef..da33d11 100644 --- a/testfixtures/firemodel/TestFiremodelFromSchema/go/test_timestamps.firemodel.go +++ b/testfixtures/firemodel/TestFiremodelFromSchema/go/test_timestamps.firemodel.go @@ -2,9 +2,14 @@ package firemodel -import "time" +import ( + "fmt" + "time" +) // TODO: Add comment to TestTimestamps +// +// Firestore document location: /timestamps/{test_timestamps_id} type TestTimestamps struct { // Creation timestamp. @@ -12,3 +17,8 @@ type TestTimestamps struct { // Update timestamp. UpdatedAt time.Time `firestore:"updatedAt,serverTimestamp"` } + +// TestTimestampsPath returns the path to a particular TestTimestamps in Firestore. +func TestTimestampsPath(testTimestampsId string) string { + return fmt.Sprintf("timestamps/%s", testTimestampsId) +} diff --git a/testfixtures/firemodel/TestFiremodelFromSchema/swift/firemodel.swift b/testfixtures/firemodel/TestFiremodelFromSchema/swift/firemodel.swift index bd2dac5..f64822b 100644 --- a/testfixtures/firemodel/TestFiremodelFromSchema/swift/firemodel.swift +++ b/testfixtures/firemodel/TestFiremodelFromSchema/swift/firemodel.swift @@ -44,8 +44,6 @@ extension TestDirection: CustomDebugStringConvertible { return "UP" case .down: return "DOWN" - default: - return nil } } @@ -53,9 +51,9 @@ extension TestDirection: CustomDebugStringConvertible { } // A Test is a test model. -@objcMembers class TestModel: Pring.Object { - static var userId: String = "" - override class var path: String { return "users/\(userId)/test_models" } +@objcMembers class TestModel: Pring.Object {static var userId: String = "" +static var testModelId: String = "" + override class var path: String { return "users/\(userId)/test_models/\(testModelId)" } // The name. dynamic var name: String? @@ -74,13 +72,15 @@ extension TestDirection: CustomDebugStringConvertible { // TODO: Add documentation to location. dynamic var location: Pring.GeoPoint? // TODO: Add documentation to colors. - dynamic var colors: [String] = [] + dynamic var colors: [String]? // TODO: Add documentation to directions. - dynamic var directions: [TestDirection] = [] + dynamic var directions: [TestDirection]? // TODO: Add documentation to models. - dynamic var models: [TestModel] = [] + dynamic var models: [TestModel]? // TODO: Add documentation to refs. - dynamic var refs: [Pring.AnyReference] = [] + dynamic var refs: [Pring.AnyReference]? + // TODO: Add documentation to modelRefs. + dynamic var modelRefs: [Pring.Reference]? // TODO: Add documentation to meta. dynamic var meta: [AnyHashable: Any] = [:] // TODO: Add documentation to metaStrs. @@ -118,9 +118,7 @@ extension TestDirection: CustomDebugStringConvertible { } // TODO: Add documentation to TestTimestamps. -@objcMembers class TestTimestamps: Pring.Object { - override class var path: String { return "" } - +@objcMembers class TestTimestamps: Pring.Object {static var testTimestampsId: String = "" + override class var path: String { return "timestamps/\(testTimestampsId)" } - } diff --git a/testfixtures/firemodel/TestFiremodelFromSchema/ts/firemodel.d.ts b/testfixtures/firemodel/TestFiremodelFromSchema/ts/firemodel.d.ts index 40b8ace..242a7d3 100644 --- a/testfixtures/firemodel/TestFiremodelFromSchema/ts/firemodel.d.ts +++ b/testfixtures/firemodel/TestFiremodelFromSchema/ts/firemodel.d.ts @@ -100,6 +100,8 @@ export namespace example { models?: ITestModel[]; /** TODO: Add documentation to refs. */ refs?: firestore.DocumentReference[]; + /** TODO: Add documentation to model_refs. */ + modelRefs?: DocumentReference[]; /** TODO: Add documentation to meta. */ meta?: { [key: string]: any; }; /** TODO: Add documentation to meta_strs. */ diff --git a/testfixtures/firemodel/TestFiremodelFromYamlSpec/swift/firemodel.swift b/testfixtures/firemodel/TestFiremodelFromYamlSpec/swift/firemodel.swift index 0d7b2a9..3d2626c 100644 --- a/testfixtures/firemodel/TestFiremodelFromYamlSpec/swift/firemodel.swift +++ b/testfixtures/firemodel/TestFiremodelFromYamlSpec/swift/firemodel.swift @@ -44,8 +44,6 @@ extension Direction: CustomDebugStringConvertible { return "UP" case .down: return "DOWN" - default: - return nil } } @@ -53,9 +51,8 @@ extension Direction: CustomDebugStringConvertible { } // A Test is a test model. -@objcMembers class TestModel: Pring.Object { - static var userId: String = "" - override class var path: String { return "users/\(userId)/test_models" } +@objcMembers class TestModel: Pring.Object {static var userId: String = "" + override class var path: String { return "users/\(userId)/test_models" } // The name. dynamic var name: String? @@ -74,7 +71,7 @@ extension Direction: CustomDebugStringConvertible { // TODO: Add documentation to location. dynamic var location: Pring.GeoPoint? // TODO: Add documentation to colors. - dynamic var colors: [String] = [] + dynamic var colors: [String]? // TODO: Add documentation to meta. dynamic var meta: [AnyHashable: Any] = [:] // TODO: Add documentation to direction. @@ -85,6 +82,4 @@ extension Direction: CustomDebugStringConvertible { dynamic var url: URL? // TODO: Add documentation to nested. dynamic var nested: Pring.NestedCollection = [] - - } diff --git a/testfixtures/schema/extras.firemodel b/testfixtures/schema/extras.firemodel index 3395902..df6c4e2 100644 --- a/testfixtures/schema/extras.firemodel +++ b/testfixtures/schema/extras.firemodel @@ -6,6 +6,8 @@ model TestModel { array primative_ary; array model_ary; array enum_ary; + array> reference_ary; + array> nested_ary; array generic_ary; map primative_map; map model_map;