From b51f26e998700a0d8809ea76f7cd6d77eda90395 Mon Sep 17 00:00:00 2001 From: Henrique Leite Date: Thu, 5 Sep 2024 18:05:09 -0300 Subject: [PATCH] fix bugs --- cli/internal/parser/types.go | 42 ++++++++++++- docs/docs/tech-decisions.md | 64 ++++++++++++++++++++ generators/atlas/internal/file.go | 12 ++-- generators/atlas/internal/postgres/parser.go | 4 +- generators/grpc/internal/enum.go | 4 +- generators/grpc/internal/file.go | 11 +++- generators/grpc/internal/message.go | 3 +- generators/grpc/internal/parser.go | 28 +++++---- 8 files changed, 141 insertions(+), 27 deletions(-) diff --git a/cli/internal/parser/types.go b/cli/internal/parser/types.go index 8bd43d2..d5e5e5a 100644 --- a/cli/internal/parser/types.go +++ b/cli/internal/parser/types.go @@ -133,6 +133,7 @@ func (self *anvToAnvpParser) resolveType(i *resolveInput) (string, error) { } var childTypesHashes []string = nil + propertiesAny, ok := vMap["Properties"] if ok { if typeType != schemas.TypeType_Map { @@ -169,11 +170,50 @@ func (self *anvToAnvpParser) resolveType(i *resolveInput) (string, error) { return typeRefI.Name < typeRefJ.Name }) - childTypesHashes = typesHashes + if childTypesHashes == nil { + childTypesHashes = typesHashes + } else { + childTypesHashes = append(childTypesHashes, typesHashes...) + } } else if typeType == schemas.TypeType_Map { return "", fmt.Errorf("Type \"%s.%s\" must have property \"Properties\". All types with map \"Type\" must.", i.path, i.k) } + itemsAny, ok := vMap["Items"] + if ok { + if typeType != schemas.TypeType_List { + return "", fmt.Errorf("Type \"%s.%s\" cannot have property \"Items\". Only types with list \"Type\" can.", i.path, i.k) + } + + itemsMap, ok := itemsAny.(map[string]any) + if !ok { + return "", fmt.Errorf("fail to parse \"%s.%s.Items\" to `map[string]any`", i.path, i.k) + } + + kk := i.k + "Item" + typeHash, err := self.resolveType(&resolveInput{ + path: fmt.Sprintf("%s.%s.Items", i.path, i.k), + ref: ref, + k: kk, + v: itemsMap, + }) + if err != nil { + return "", err + } + typeRef := self.schema.Types.Types[typeHash] + if typeRef == nil { + return "", fmt.Errorf("fail to find type \"%s.%s.Items.%s\"`", i.path, i.k, kk) + } + + if childTypesHashes == nil { + childTypesHashes = []string{typeHash} + } else { + childTypesHashes = append(childTypesHashes, typeHash) + } + } else if typeType == schemas.TypeType_List { + return "", fmt.Errorf("Type \"%s.%s\" must have property \"Items\". All types with list \"Type\" must.", i.path, i.k) + } + var enumHash *string = nil valuesAny, ok := vMap["Values"] if ok { diff --git a/docs/docs/tech-decisions.md b/docs/docs/tech-decisions.md index 83f5ebf..550c4b9 100644 --- a/docs/docs/tech-decisions.md +++ b/docs/docs/tech-decisions.md @@ -22,3 +22,67 @@ while having to write the least amount of code. In HTTP status code specification, it's very explicit that StatusCode 204 should not return any content, so instead of having a way to configure it, Anvil already handles it for you, ensuring that you follow best practices. + +### Problems with ordering + +To have the best UX writing the schema in Yaml, we opted to use Maps instead of Lists in many of the cases, here's an example: + +With Maps, the current structure of `Entities` is this: +```yaml +Entities: + Entities: + User: + Name: users + Columns: + Id: + Type: Int +``` + +And with Lists, it would be like this: +```yaml +Entities: + Entities: + - Name: User + Name: users + Columns: + - Name: Id + Type: Int +``` + +It doesn't change that much, but we fear that the `$ref` would be confuse, because you must use the `Name` and not the `index`, what may be confusing. + +Using Maps has a problem: We are unable to keep the order of the things, due to limitations of the data structure. We could create our own parse for the YAML file, but the YAML specification is too complex for us to do it while maintaining Anvil and it's generators, so we opted for an work around, the `Order` prop: + +```yaml +Entities: + Entities: + User: + Name: users + Order: 0 + Columns: + Id: + Type: Int + Order: 0 +``` + +If you don't want to use the default ordering (alphabetical), you can set the order manually. It doesn't need to follow a specific order, it can have any value that you want, as long as it's an `int32`. + +If you are using grpc and has deprecated fields, you can use `Order` to ignore these fields, like this: + +```yaml +Entities: + Entities: + User: + Name: users + Order: 0 + Columns: + Id: + Type: Int + Order: 0 + Foo: + Type: String + Order: 1 + Bar: + Type: String + Order: 3 # Not a problem, will work fine +``` diff --git a/generators/atlas/internal/file.go b/generators/atlas/internal/file.go index d973fd8..16ce259 100644 --- a/generators/atlas/internal/file.go +++ b/generators/atlas/internal/file.go @@ -1,7 +1,6 @@ package internal import ( - "fmt" "os" "github.com/henriqueleite42/anvil/cli/formatter" @@ -17,14 +16,17 @@ func WriteHclFile(path string, schema *schemas.Schema, content string) error { domainKebab := formatter.PascalToKebab(schema.Domain) if path == "" { - path = myDir + "/" + domainKebab + ".hcl" + path = myDir } else { - path = myDir + "/" + path + "/" + domainKebab + ".hcl" + path = myDir + "/" + path } - fmt.Println(path) + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return err + } - err = os.WriteFile(path, []byte(content), 0644) + err = os.WriteFile(path+"/"+domainKebab+".hcl", []byte(content), 0644) if err != nil { return err } diff --git a/generators/atlas/internal/postgres/parser.go b/generators/atlas/internal/postgres/parser.go index 559974c..43e102c 100644 --- a/generators/atlas/internal/postgres/parser.go +++ b/generators/atlas/internal/postgres/parser.go @@ -24,13 +24,13 @@ func (self *hclFile) toString() string { for _, v := range self.dbSchemas { schemasArr = append(schemasArr, v) } - schemas := strings.Join(schemasArr, "\n\n") + schemas := strings.Join(schemasArr, "\n") enumsArr := []string{} for _, v := range self.enums { enumsArr = append(enumsArr, v) } - enums := strings.Join(enumsArr, "\n\n") + enums := strings.Join(enumsArr, "\n") return fmt.Sprintf(`%s diff --git a/generators/grpc/internal/enum.go b/generators/grpc/internal/enum.go index de0c544..7bc4dbf 100644 --- a/generators/grpc/internal/enum.go +++ b/generators/grpc/internal/enum.go @@ -14,11 +14,11 @@ func (self *protoFile) resolveEnum(e *schemas.Enum) string { values := []string{} for k, v := range e.Values { - values = append(values, fmt.Sprintf("%s = %d;", v.Value, k)) + values = append(values, fmt.Sprintf(" %s = %d;", v.Value, k)) } self.enums[e.Name] = fmt.Sprintf(`enum %s { - %s +%s }`, e.Name, strings.Join(values, "\n")) return e.Name diff --git a/generators/grpc/internal/file.go b/generators/grpc/internal/file.go index 2035609..ea35363 100644 --- a/generators/grpc/internal/file.go +++ b/generators/grpc/internal/file.go @@ -16,12 +16,17 @@ func WriteProtoFile(path string, schema *schemas.Schema, content string) error { domainKebab := formatter.PascalToKebab(schema.Domain) if path == "" { - path = myDir + "/" + domainKebab + ".hcl" + path = myDir + "/" } else { - path = myDir + "/" + path + "/" + domainKebab + ".hcl" + path = myDir + "/" + path } - err = os.WriteFile(path, []byte(content), 0644) + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return err + } + + err = os.WriteFile(path+"/"+domainKebab+".proto", []byte(content), 0644) if err != nil { return err } diff --git a/generators/grpc/internal/message.go b/generators/grpc/internal/message.go index 664f55c..f7db9e2 100644 --- a/generators/grpc/internal/message.go +++ b/generators/grpc/internal/message.go @@ -59,6 +59,7 @@ func (self *protoFile) resolveMsgPropType(sourceTypeRef string) (string, error) } if refType.Type == schemas.TypeType_List { if refType.ChildTypesHashes == nil { + fmt.Println(*refType) return "", fmt.Errorf("type \"%s\" is missing prop \"ChildTypesHashes\"", sourceTypeRef) } if len(refType.ChildTypesHashes) != 1 { @@ -70,7 +71,7 @@ func (self *protoFile) resolveMsgPropType(sourceTypeRef string) (string, error) return "", err } - typeString = fmt.Sprintf("[]%s", typeName) + typeString = fmt.Sprintf("repeated %s", typeName) } if typeString == "" { return "", fmt.Errorf("fail to find type for \"%s\"", refType.Type) diff --git a/generators/grpc/internal/parser.go b/generators/grpc/internal/parser.go index 1af717e..ac0549b 100644 --- a/generators/grpc/internal/parser.go +++ b/generators/grpc/internal/parser.go @@ -30,7 +30,12 @@ func (self *protoFile) toString() string { }) var imports string for _, v := range sortedImports { - imports += v + "\n" + imports += "\n" + v + } + + service := self.service + if imports != "" { + service = "\n" + service } sortedEnums := []string{} @@ -42,7 +47,7 @@ func (self *protoFile) toString() string { }) var enums string for _, v := range sortedEnums { - enums += v + "\n" + enums += "\n" + v } sortedMessages := []string{} @@ -54,26 +59,23 @@ func (self *protoFile) toString() string { }) var messages string for _, v := range sortedMessages { - messages += v + "\n" + messages += "\n" + v } - return fmt.Sprintf(`syntax = "proto3"; - -%s + if enums != "" { + messages = "\n" + messages + } + return fmt.Sprintf(`syntax = "proto3"; %s - %s - -%s`, imports, self.service, enums, messages) +%s%s`, imports, service, enums, messages) } func Parse(schema *schemas.Schema) (string, error) { proto := &protoFile{ - schema: schema, - imports: map[string]bool{ - " \"time\"": true, - }, + schema: schema, + imports: map[string]bool{}, enums: map[string]string{}, messages: map[string]string{}, }