From 3d56faa79da7644fea32b9a4c0a50afec047e55f Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Thu, 8 Feb 2024 20:12:03 +1100 Subject: [PATCH 1/2] fix: fix weird case transforms like FTLName -> fTLName This should now be determinstically mapped to upper/lower camel for types and functions/values respectively. Note that this casing differs from native Go casing in that initialisms/acronyms are never all-caps. That is, FTL casing is: Ftl not FTL. --- backend/schema/protobuf.go | 3 +- backend/schema/strcase/case.go | 164 +++++++++++++++ backend/schema/strcase/case_test.go | 191 ++++++++++++++++++ cmd/ftl/cmd_init.go | 15 +- .../protos/google/protobuf/timestamp_pb.ts | 2 +- .../metrics/v1/metrics_service_pb.ts | 2 +- .../proto/common/v1/common_pb.ts | 2 +- .../proto/metrics/v1/metrics_pb.ts | 2 +- .../proto/resource/v1/resource_pb.ts | 2 +- .../opentelemetry/proto/trace/v1/trace_pb.ts | 2 +- .../build-template/_ftl.tmpl/go/main/main.go | 2 +- go-runtime/compile/build.go | 37 +++- go-runtime/compile/schema.go | 37 ++-- go-runtime/compile/schema_test.go | 4 +- go-runtime/encoding/encoding.go | 2 +- go-runtime/sdk/call.go | 3 +- integration/integration_test.go | 3 +- renovate.json5 | 6 - 18 files changed, 430 insertions(+), 49 deletions(-) create mode 100644 backend/schema/strcase/case.go create mode 100644 backend/schema/strcase/case_test.go diff --git a/backend/schema/protobuf.go b/backend/schema/protobuf.go index 143e619d12..7dff04363e 100644 --- a/backend/schema/protobuf.go +++ b/backend/schema/protobuf.go @@ -6,9 +6,10 @@ import ( "strconv" "strings" - "github.com/iancoleman/strcase" "golang.org/x/exp/maps" "golang.org/x/exp/slices" + + "github.com/TBD54566975/ftl/backend/schema/strcase" ) var typesWithRuntime = map[string]bool{ diff --git a/backend/schema/strcase/case.go b/backend/schema/strcase/case.go new file mode 100644 index 0000000000..2d58850810 --- /dev/null +++ b/backend/schema/strcase/case.go @@ -0,0 +1,164 @@ +// Package strcase provides programming case conversion functions for strings. +// +// These case conversion functions are used to deterministically convert strings +// to various programming cases. +package strcase + +// NOTE: This code is from https://github.com/fatih/camelcase. MIT license. + +import ( + "strings" + "unicode" + "unicode/utf8" +) + +func title(s string) string { + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToTitle(r)) + strings.ToLower(s[n:]) +} + +func ToLowerCamel(s string) string { + parts := split(s) + for i := range parts { + parts[i] = title(parts[i]) + } + return strings.ToLower(parts[0]) + strings.Join(parts[1:], "") +} + +func ToUpperCamel(s string) string { + parts := split(s) + for i := range parts { + parts[i] = title(parts[i]) + } + return strings.Join(parts, "") +} + +func ToLowerSnake(s string) string { + parts := split(s) + out := make([]string, 0, len(parts)*2) + for i := range parts { + if parts[i] == "_" { + continue + } + out = append(out, strings.ToLower(parts[i])) + } + return strings.Join(out, "_") +} + +func ToUpperSnake(s string) string { + parts := split(s) + out := make([]string, 0, len(parts)*2) + for i := range parts { + if parts[i] == "_" { + continue + } + out = append(out, strings.ToUpper(parts[i])) + } + return strings.Join(out, "_") +} + +func ToLowerKebab(s string) string { + parts := split(s) + out := make([]string, 0, len(parts)*2) + for i := range parts { + if parts[i] == "-" || parts[i] == "_" { + continue + } + out = append(out, strings.ToLower(parts[i])) + } + return strings.Join(out, "-") +} + +func ToUpperKebab(s string) string { + parts := split(s) + out := make([]string, 0, len(parts)*2) + for i := range parts { + if parts[i] == "-" || parts[i] == "_" { + continue + } + out = append(out, strings.ToUpper(parts[i])) + } + return strings.Join(out, "-") +} + +// Splits a camelcase word and returns a list of words. It also +// supports digits. Both lower camel case and upper camel case are supported. +// For more info please check: http://en.wikipedia.org/wiki/CamelCase +// +// Examples +// +// "" => [""] +// "lowercase" => ["lowercase"] +// "Class" => ["Class"] +// "MyClass" => ["My", "Class"] +// "MyC" => ["My", "C"] +// "HTML" => ["HTML"] +// "PDFLoader" => ["PDF", "Loader"] +// "AString" => ["A", "String"] +// "SimpleXMLParser" => ["Simple", "XML", "Parser"] +// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] +// "GL11Version" => ["GL", "11", "Version"] +// "99Bottles" => ["99", "Bottles"] +// "May5" => ["May", "5"] +// "BFG9000" => ["BFG", "9000"] +// "BöseÜberraschung" => ["Böse", "Überraschung"] +// "Two spaces" => ["Two", " ", "spaces"] +// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +// +// Splitting rules +// +// 1. If string is not valid UTF-8, return it without splitting as +// single item array. +// 2. Assign all unicode characters into one of 4 sets: lower case +// letters, upper case letters, numbers, and all other characters. +// 3. Iterate through characters of string, introducing splits +// between adjacent characters that belong to different sets. +// 4. Iterate through array of split strings, and if a given string +// is upper case: +// if subsequent string is lower case: +// move last character of upper case string to beginning of +// lower case string +func split(src string) (entries []string) { + // don't split invalid utf8 + if !utf8.ValidString(src) { + return []string{src} + } + entries = []string{} + var runes [][]rune + lastClass := 0 + // split into fields based on class of unicode character + for _, r := range src { + var class int + switch { + case unicode.IsLower(r): + class = 1 + case unicode.IsUpper(r): + class = 2 + case unicode.IsDigit(r): + class = 3 + default: + class = 4 + } + if class == lastClass { + runes[len(runes)-1] = append(runes[len(runes)-1], r) + } else { + runes = append(runes, []rune{r}) + } + lastClass = class + } + // handle upper case -> lower case sequences, e.g. + // "PDFL", "oader" -> "PDF", "Loader" + for i := 0; i < len(runes)-1; i++ { + if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { + runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) + runes[i] = runes[i][:len(runes[i])-1] + } + } + // construct []string from results + for _, s := range runes { + if len(s) > 0 { + entries = append(entries, string(s)) + } + } + return entries +} diff --git a/backend/schema/strcase/case_test.go b/backend/schema/strcase/case_test.go new file mode 100644 index 0000000000..4a9b88d7d5 --- /dev/null +++ b/backend/schema/strcase/case_test.go @@ -0,0 +1,191 @@ +package strcase + +import ( + "testing" + + "github.com/alecthomas/assert/v2" +) + +func TestCamelCase(t *testing.T) { + for _, tt := range []struct { + input string + expected []string + }{ + {"lowercase", []string{"lowercase"}}, + {"Class", []string{"Class"}}, + {"MyClass", []string{"My", "Class"}}, + {"MyC", []string{"My", "C"}}, + {"HTML", []string{"HTML"}}, + {"PDFLoader", []string{"PDF", "Loader"}}, + {"AString", []string{"A", "String"}}, + {"SimpleXMLParser", []string{"Simple", "XML", "Parser"}}, + {"vimRPCPlugin", []string{"vim", "RPC", "Plugin"}}, + {"GL11Version", []string{"GL", "11", "Version"}}, + {"99Bottles", []string{"99", "Bottles"}}, + {"May5", []string{"May", "5"}}, + {"BFG9000", []string{"BFG", "9000"}}, + {"BöseÜberraschung", []string{"Böse", "Überraschung"}}, + {"Two spaces", []string{"Two", " ", "spaces"}}, + {"BadUTF8\xe2\xe2\xa1", []string{"BadUTF8\xe2\xe2\xa1"}}, + {"snake_case", []string{"snake", "_", "case"}}, + } { + actual := split(tt.input) + assert.Equal(t, tt.expected, actual, "camelCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestLowerCamelCase(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "lowercase"}, + {"Class", "class"}, + {"MyClass", "myClass"}, + {"MyC", "myC"}, + {"HTML", "html"}, + {"PDFLoader", "pdfLoader"}, + {"AString", "aString"}, + {"SimpleXMLParser", "simpleXmlParser"}, + {"vimRPCPlugin", "vimRpcPlugin"}, + {"GL11Version", "gl11Version"}, + {"99Bottles", "99Bottles"}, + {"May5", "may5"}, + {"BFG9000", "bfg9000"}, + {"BöseÜberraschung", "böseÜberraschung"}, + {"snake_case", "snake_Case"}, + } { + actual := ToLowerCamel(tt.input) + assert.Equal(t, tt.expected, actual, "LowerCamelCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestUpperCamelCase(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "Lowercase"}, + {"Class", "Class"}, + {"MyClass", "MyClass"}, + {"MyC", "MyC"}, + {"HTML", "Html"}, + {"PDFLoader", "PdfLoader"}, + {"AString", "AString"}, + {"SimpleXMLParser", "SimpleXmlParser"}, + {"vimRPCPlugin", "VimRpcPlugin"}, + {"GL11Version", "Gl11Version"}, + {"99Bottles", "99Bottles"}, + {"May5", "May5"}, + {"BFG9000", "Bfg9000"}, + {"BöseÜberraschung", "BöseÜberraschung"}, + {"snake_case", "Snake_Case"}, + } { + actual := ToUpperCamel(tt.input) + assert.Equal(t, tt.expected, actual, "UpperCamelCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestLowerSnake(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "lowercase"}, + {"Class", "class"}, + {"MyClass", "my_class"}, + {"MyC", "my_c"}, + {"HTML", "html"}, + {"PDFLoader", "pdf_loader"}, + {"AString", "a_string"}, + {"SimpleXMLParser", "simple_xml_parser"}, + {"vimRPCPlugin", "vim_rpc_plugin"}, + {"GL11Version", "gl_11_version"}, + {"99Bottles", "99_bottles"}, + {"May5", "may_5"}, + {"BFG9000", "bfg_9000"}, + {"BöseÜberraschung", "böse_überraschung"}, + {"snake_case", "snake_case"}, + } { + actual := ToLowerSnake(tt.input) + assert.Equal(t, tt.expected, actual, "LowerSnakeCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestUpperSnake(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "LOWERCASE"}, + {"Class", "CLASS"}, + {"MyClass", "MY_CLASS"}, + {"MyC", "MY_C"}, + {"HTML", "HTML"}, + {"PDFLoader", "PDF_LOADER"}, + {"AString", "A_STRING"}, + {"SimpleXMLParser", "SIMPLE_XML_PARSER"}, + {"vimRPCPlugin", "VIM_RPC_PLUGIN"}, + {"GL11Version", "GL_11_VERSION"}, + {"99Bottles", "99_BOTTLES"}, + {"May5", "MAY_5"}, + {"BFG9000", "BFG_9000"}, + {"BöseÜberraschung", "BÖSE_ÜBERRASCHUNG"}, + {"snake_case", "SNAKE_CASE"}, + } { + actual := ToUpperSnake(tt.input) + assert.Equal(t, tt.expected, actual, "UpperSnakeCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestLowerKebabCase(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "lowercase"}, + {"Class", "class"}, + {"MyClass", "my-class"}, + {"MyC", "my-c"}, + {"HTML", "html"}, + {"PDFLoader", "pdf-loader"}, + {"AString", "a-string"}, + {"SimpleXMLParser", "simple-xml-parser"}, + {"vimRPCPlugin", "vim-rpc-plugin"}, + {"GL11Version", "gl-11-version"}, + {"99Bottles", "99-bottles"}, + {"May5", "may-5"}, + {"BFG9000", "bfg-9000"}, + {"BöseÜberraschung", "böse-überraschung"}, + {"snake_case", "snake-case"}, + } { + actual := ToLowerKebab(tt.input) + assert.Equal(t, tt.expected, actual, "LowerKebabCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + +func TestUpperKebabCase(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "LOWERCASE"}, + {"Class", "CLASS"}, + {"MyClass", "MY-CLASS"}, + {"MyC", "MY-C"}, + {"HTML", "HTML"}, + {"PDFLoader", "PDF-LOADER"}, + {"AString", "A-STRING"}, + {"SimpleXMLParser", "SIMPLE-XML-PARSER"}, + {"vimRPCPlugin", "VIM-RPC-PLUGIN"}, + {"GL11Version", "GL-11-VERSION"}, + {"99Bottles", "99-BOTTLES"}, + {"May5", "MAY-5"}, + {"BFG9000", "BFG-9000"}, + {"BöseÜberraschung", "BÖSE-ÜBERRASCHUNG"}, + {"snake_case", "SNAKE-CASE"}, + } { + actual := ToUpperKebab(tt.input) + assert.Equal(t, tt.expected, actual, "UpperKebabCase(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} diff --git a/cmd/ftl/cmd_init.go b/cmd/ftl/cmd_init.go index 1d4d4f0c89..f9652de837 100644 --- a/cmd/ftl/cmd_init.go +++ b/cmd/ftl/cmd_init.go @@ -11,9 +11,10 @@ import ( "path/filepath" "strings" - "github.com/TBD54566975/scaffolder" "github.com/beevik/etree" - "github.com/iancoleman/strcase" + + "github.com/TBD54566975/ftl/backend/schema/strcase" + "github.com/TBD54566975/scaffolder" "github.com/TBD54566975/ftl/backend/common/exec" "github.com/TBD54566975/ftl/backend/common/log" @@ -144,12 +145,12 @@ func scaffold(hermit bool, source *zip.Reader, destination string, ctx any, opti } var scaffoldFuncs = template.FuncMap{ - "snake": strcase.ToSnake, - "screamingSnake": strcase.ToScreamingSnake, - "camel": strcase.ToCamel, + "snake": strcase.ToLowerSnake, + "screamingSnake": strcase.ToUpperSnake, + "camel": strcase.ToUpperCamel, "lowerCamel": strcase.ToLowerCamel, - "kebab": strcase.ToKebab, - "screamingKebab": strcase.ToScreamingKebab, + "kebab": strcase.ToLowerKebab, + "screamingKebab": strcase.ToUpperKebab, "upper": strings.ToUpper, "lower": strings.ToLower, "title": strings.Title, diff --git a/frontend/src/protos/google/protobuf/timestamp_pb.ts b/frontend/src/protos/google/protobuf/timestamp_pb.ts index 0ed7a5197b..3010f82729 100644 --- a/frontend/src/protos/google/protobuf/timestamp_pb.ts +++ b/frontend/src/protos/google/protobuf/timestamp_pb.ts @@ -28,7 +28,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file google/protobuf/timestamp.proto (package google.protobuf, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/frontend/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts b/frontend/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts index dbd6f0811b..c527ebd4c9 100644 --- a/frontend/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +++ b/frontend/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file opentelemetry/proto/collector/metrics/v1/metrics_service.proto (package opentelemetry.proto.collector.metrics.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/frontend/src/protos/opentelemetry/proto/common/v1/common_pb.ts b/frontend/src/protos/opentelemetry/proto/common/v1/common_pb.ts index 6fd528c6f3..059a392883 100644 --- a/frontend/src/protos/opentelemetry/proto/common/v1/common_pb.ts +++ b/frontend/src/protos/opentelemetry/proto/common/v1/common_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file opentelemetry/proto/common/v1/common.proto (package opentelemetry.proto.common.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/frontend/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts b/frontend/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts index a6457f7298..579c98d866 100644 --- a/frontend/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts +++ b/frontend/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file opentelemetry/proto/metrics/v1/metrics.proto (package opentelemetry.proto.metrics.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/frontend/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts b/frontend/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts index 72d4033a0c..37043413f8 100644 --- a/frontend/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts +++ b/frontend/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file opentelemetry/proto/resource/v1/resource.proto (package opentelemetry.proto.resource.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/frontend/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts b/frontend/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts index 94b22ef749..f2f9e8d61b 100644 --- a/frontend/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts +++ b/frontend/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.7.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.7.2 with parameter "target=ts" // @generated from file opentelemetry/proto/trace/v1/trace.proto (package opentelemetry.proto.trace.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/go-runtime/compile/build-template/_ftl.tmpl/go/main/main.go b/go-runtime/compile/build-template/_ftl.tmpl/go/main/main.go index 84c2a3c288..c5d58458fa 100644 --- a/go-runtime/compile/build-template/_ftl.tmpl/go/main/main.go +++ b/go-runtime/compile/build-template/_ftl.tmpl/go/main/main.go @@ -14,7 +14,7 @@ import ( func main() { verbConstructor := server.NewUserVerbServer("{{.Name}}", {{- range .Verbs}} - server.Handle({{$.Name}}.{{.Name|camel}}), + server.Handle({{$.Name}}.{{.Name}}), {{- end}} ) plugin.Start(context.Background(), "{{.Name}}", verbConstructor, ftlv1connect.VerbServiceName, ftlv1connect.NewVerbServiceHandler) diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index 05dad42e5d..18139346cf 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -10,11 +10,12 @@ import ( "reflect" "strings" - "github.com/TBD54566975/scaffolder" - "github.com/iancoleman/strcase" "golang.org/x/mod/modfile" "google.golang.org/protobuf/proto" + "github.com/TBD54566975/ftl/backend/schema/strcase" + "github.com/TBD54566975/scaffolder" + "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/backend/common/exec" "github.com/TBD54566975/ftl/backend/common/log" @@ -30,9 +31,14 @@ type externalModuleContext struct { Main string } +type goVerb struct { + Name string +} + type mainModuleContext struct { GoVersion string - *schema.Module + Name string + Verbs []goVerb } func (b externalModuleContext) NonMainModules() []*schema.Module { @@ -84,7 +90,7 @@ func Build(ctx context.Context, moduleDir string, sch *schema.Schema) error { } logger.Debugf("Extracting schema") - main, err := ExtractModuleSchema(moduleDir) + nativeNames, main, err := ExtractModuleSchema(moduleDir) if err != nil { return fmt.Errorf("failed to extract module schema: %w", err) } @@ -98,9 +104,20 @@ func Build(ctx context.Context, moduleDir string, sch *schema.Schema) error { } logger.Debugf("Generating main module") + goVerbs := make([]goVerb, 0, len(main.Decls)) + for _, decl := range main.Decls { + if verb, ok := decl.(*schema.Verb); ok { + nativeName, ok := nativeNames[verb] + if !ok { + return fmt.Errorf("missing native name for verb %s", verb.Name) + } + goVerbs = append(goVerbs, goVerb{Name: nativeName}) + } + } if err := internal.ScaffoldZip(buildTemplateFiles(), moduleDir, mainModuleContext{ GoVersion: goModVersion, - Module: main, + Name: main.Name, + Verbs: goVerbs, }, scaffolder.Exclude("^go.mod$"), scaffolder.Functions(funcs)); err != nil { return err } @@ -115,12 +132,12 @@ func Build(ctx context.Context, moduleDir string, sch *schema.Schema) error { } var scaffoldFuncs = scaffolder.FuncMap{ - "snake": strcase.ToSnake, - "screamingSnake": strcase.ToScreamingSnake, - "camel": strcase.ToCamel, + "snake": strcase.ToLowerSnake, + "screamingSnake": strcase.ToUpperSnake, + "camel": strcase.ToUpperCamel, "lowerCamel": strcase.ToLowerCamel, - "kebab": strcase.ToKebab, - "screamingKebab": strcase.ToScreamingKebab, + "kebab": strcase.ToLowerKebab, + "screamingKebab": strcase.ToUpperKebab, "upper": strings.ToUpper, "lower": strings.ToLower, "title": strings.Title, diff --git a/go-runtime/compile/schema.go b/go-runtime/compile/schema.go index 2ab39ed698..591c27d571 100644 --- a/go-runtime/compile/schema.go +++ b/go-runtime/compile/schema.go @@ -11,10 +11,11 @@ import ( "strings" "sync" - "github.com/iancoleman/strcase" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/packages" + "github.com/TBD54566975/ftl/backend/schema/strcase" + "github.com/TBD54566975/ftl/backend/common/goast" "github.com/TBD54566975/ftl/backend/schema" ) @@ -32,25 +33,29 @@ var ( aliasFieldTag = "alias" ) +// NativeNames is a map of top-level declarations to their native Go names. +type NativeNames map[schema.Decl]string + // ExtractModuleSchema statically parses Go FTL module source into a schema.Module. -func ExtractModuleSchema(dir string) (*schema.Module, error) { +func ExtractModuleSchema(dir string) (NativeNames, *schema.Module, error) { pkgs, err := packages.Load(&packages.Config{ Dir: dir, Fset: fset, Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo, }, "./...") if err != nil { - return &schema.Module{}, err + return nil, nil, err } if len(pkgs) == 0 { - return &schema.Module{}, fmt.Errorf("no packages found in %q, does \"go mod tidy\" need to be run?", dir) + return nil, nil, fmt.Errorf("no packages found in %q, does \"go mod tidy\" need to be run?", dir) } + nativeNames := NativeNames{} module := &schema.Module{} for _, pkg := range pkgs { if len(pkg.Errors) > 0 { - return nil, fmt.Errorf("%s: %w", pkg.PkgPath, pkg.Errors[0]) + return nil, nil, fmt.Errorf("%s: %w", pkg.PkgPath, pkg.Errors[0]) } - pctx := &parseContext{pkg: pkg, pkgs: pkgs, module: module} + pctx := &parseContext{pkg: pkg, pkgs: pkgs, module: module, nativeNames: NativeNames{}} for _, file := range pkg.Syntax { var verb *schema.Verb err := goast.Visit(file, func(node ast.Node, next func() error) (err error) { @@ -91,14 +96,17 @@ func ExtractModuleSchema(dir string) (*schema.Module, error) { return next() }) if err != nil { - return nil, err + return nil, nil, err } } + for decl, nativeName := range pctx.nativeNames { + nativeNames[decl] = nativeName + } } if module.Name == "" { - return module, fmt.Errorf("//ftl:module directive is required") + return nil, nil, fmt.Errorf("//ftl:module directive is required") } - return module, schema.ValidateModule(module) + return nativeNames, module, schema.ValidateModule(module) } func visitCallExpr(pctx *parseContext, verb *schema.Verb, node *ast.CallExpr) error { @@ -280,6 +288,7 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb, e Response: resp, Metadata: metadata, } + pctx.nativeNames[verb] = node.Name.Name pctx.module.Decls = append(pctx.module.Decls, verb) return verb, nil } @@ -342,8 +351,9 @@ func visitStruct(pctx *parseContext, node ast.Node, tnode types.Type) (*schema.D out := &schema.Data{ Pos: goPosToSchemaPos(node.Pos()), - Name: named.Obj().Name(), + Name: strcase.ToUpperCamel(named.Obj().Name()), } + pctx.nativeNames[out] = named.Obj().Name() dataRef := &schema.DataRef{ Pos: goPosToSchemaPos(node.Pos()), Name: out.Name, @@ -552,9 +562,10 @@ func deref[T types.Object](pkg *packages.Package, node ast.Expr) (string, T) { } type parseContext struct { - pkg *packages.Package - pkgs []*packages.Package - module *schema.Module + pkg *packages.Package + pkgs []*packages.Package + module *schema.Module + nativeNames NativeNames } // pathEnclosingInterval returns the PackageInfo and ast.Node that diff --git a/go-runtime/compile/schema_test.go b/go-runtime/compile/schema_test.go index e87d620a75..c4e29fdc83 100644 --- a/go-runtime/compile/schema_test.go +++ b/go-runtime/compile/schema_test.go @@ -15,7 +15,7 @@ import ( ) func TestExtractModuleSchema(t *testing.T) { - actual, err := ExtractModuleSchema("testdata/one") + _, actual, err := ExtractModuleSchema("testdata/one") assert.NoError(t, err) actual = schema.Normalise(actual) expected := `module one { @@ -46,7 +46,7 @@ func TestExtractModuleSchema(t *testing.T) { } func TestExtractModuleSchemaTwo(t *testing.T) { - actual, err := ExtractModuleSchema("testdata/two") + _, actual, err := ExtractModuleSchema("testdata/two") fmt.Println(actual) assert.NoError(t, err) actual = schema.Normalise(actual) diff --git a/go-runtime/encoding/encoding.go b/go-runtime/encoding/encoding.go index 401b532599..99cd0aceef 100644 --- a/go-runtime/encoding/encoding.go +++ b/go-runtime/encoding/encoding.go @@ -10,7 +10,7 @@ import ( "fmt" "reflect" - "github.com/iancoleman/strcase" + "github.com/TBD54566975/ftl/backend/schema/strcase" ) var ( diff --git a/go-runtime/sdk/call.go b/go-runtime/sdk/call.go index 6809bd3667..02a70e499e 100644 --- a/go-runtime/sdk/call.go +++ b/go-runtime/sdk/call.go @@ -9,7 +9,8 @@ import ( "strings" "connectrpc.com/connect" - "github.com/iancoleman/strcase" + + "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/ftl/backend/common/rpc" "github.com/TBD54566975/ftl/go-runtime/encoding" diff --git a/integration/integration_test.go b/integration/integration_test.go index f1e783f747..e56ac3d936 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -23,9 +23,10 @@ import ( "connectrpc.com/connect" "github.com/alecthomas/assert/v2" _ "github.com/amacneil/dbmate/v2/pkg/driver/postgres" - "github.com/iancoleman/strcase" _ "github.com/jackc/pgx/v5/stdlib" // SQL driver + "github.com/TBD54566975/ftl/backend/schema/strcase" + "github.com/TBD54566975/ftl/backend/common/exec" "github.com/TBD54566975/ftl/backend/common/log" "github.com/TBD54566975/ftl/backend/common/rpc" diff --git a/renovate.json5 b/renovate.json5 index 25c39be3bd..ba70e46828 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -14,11 +14,5 @@ matchManagers: ["hermit"], enabled: false, }, - // strcase 0.3 changes the semantics of acronyms, so we want to stick with 0.2 for now - { - matchPackageNames: ["github.com/iancoleman/strcase"], - matchManagers: ["gomod"], - enabled: false, - }, ], } From 1931adf8848a6b2e8917112587fbf74f2d91a768 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 8 Feb 2024 09:25:26 +0000 Subject: [PATCH 2/2] chore(autofmt): Automated formatting --- cmd/ftl/cmd_init.go | 5 ++--- examples/go/echo/go.mod | 1 - examples/go/echo/go.sum | 2 -- examples/online-boutique/services/checkout/go.mod | 1 - examples/online-boutique/services/checkout/go.sum | 2 -- examples/online-boutique/services/recommendation/go.mod | 1 - examples/online-boutique/services/recommendation/go.sum | 2 -- go-runtime/compile/build.go | 5 ++--- go-runtime/compile/schema.go | 3 +-- go-runtime/sdk/call.go | 3 +-- go.mod | 1 - go.sum | 2 -- 12 files changed, 6 insertions(+), 22 deletions(-) diff --git a/cmd/ftl/cmd_init.go b/cmd/ftl/cmd_init.go index f9652de837..67854d8a1d 100644 --- a/cmd/ftl/cmd_init.go +++ b/cmd/ftl/cmd_init.go @@ -11,14 +11,13 @@ import ( "path/filepath" "strings" - "github.com/beevik/etree" - - "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/scaffolder" + "github.com/beevik/etree" "github.com/TBD54566975/ftl/backend/common/exec" "github.com/TBD54566975/ftl/backend/common/log" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/backend/schema/strcase" goruntime "github.com/TBD54566975/ftl/go-runtime" "github.com/TBD54566975/ftl/internal" kotlinruntime "github.com/TBD54566975/ftl/kotlin-runtime" diff --git a/examples/go/echo/go.mod b/examples/go/echo/go.mod index ce93dee6ef..422ee09c3b 100644 --- a/examples/go/echo/go.mod +++ b/examples/go/echo/go.mod @@ -19,7 +19,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/iancoleman/strcase v0.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.5.3 // indirect diff --git a/examples/go/echo/go.sum b/examples/go/echo/go.sum index 75422d5c2f..f7229dc660 100644 --- a/examples/go/echo/go.sum +++ b/examples/go/echo/go.sum @@ -42,8 +42,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/examples/online-boutique/services/checkout/go.mod b/examples/online-boutique/services/checkout/go.mod index d552272ce1..e5b575e218 100644 --- a/examples/online-boutique/services/checkout/go.mod +++ b/examples/online-boutique/services/checkout/go.mod @@ -24,7 +24,6 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/iancoleman/strcase v0.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.5.3 // indirect diff --git a/examples/online-boutique/services/checkout/go.sum b/examples/online-boutique/services/checkout/go.sum index 75422d5c2f..f7229dc660 100644 --- a/examples/online-boutique/services/checkout/go.sum +++ b/examples/online-boutique/services/checkout/go.sum @@ -42,8 +42,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/examples/online-boutique/services/recommendation/go.mod b/examples/online-boutique/services/recommendation/go.mod index 3fd3634e5a..61f3303ae7 100644 --- a/examples/online-boutique/services/recommendation/go.mod +++ b/examples/online-boutique/services/recommendation/go.mod @@ -21,7 +21,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/iancoleman/strcase v0.2.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.5.3 // indirect diff --git a/examples/online-boutique/services/recommendation/go.sum b/examples/online-boutique/services/recommendation/go.sum index 75422d5c2f..f7229dc660 100644 --- a/examples/online-boutique/services/recommendation/go.sum +++ b/examples/online-boutique/services/recommendation/go.sum @@ -42,8 +42,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index 18139346cf..cd0aa42a16 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -10,17 +10,16 @@ import ( "reflect" "strings" + "github.com/TBD54566975/scaffolder" "golang.org/x/mod/modfile" "google.golang.org/protobuf/proto" - "github.com/TBD54566975/ftl/backend/schema/strcase" - "github.com/TBD54566975/scaffolder" - "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/backend/common/exec" "github.com/TBD54566975/ftl/backend/common/log" "github.com/TBD54566975/ftl/backend/common/moduleconfig" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/ftl/internal" ) diff --git a/go-runtime/compile/schema.go b/go-runtime/compile/schema.go index 591c27d571..abf25e1e50 100644 --- a/go-runtime/compile/schema.go +++ b/go-runtime/compile/schema.go @@ -14,10 +14,9 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/packages" - "github.com/TBD54566975/ftl/backend/schema/strcase" - "github.com/TBD54566975/ftl/backend/common/goast" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/backend/schema/strcase" ) var ( diff --git a/go-runtime/sdk/call.go b/go-runtime/sdk/call.go index 02a70e499e..5d880c79bf 100644 --- a/go-runtime/sdk/call.go +++ b/go-runtime/sdk/call.go @@ -10,9 +10,8 @@ import ( "connectrpc.com/connect" - "github.com/TBD54566975/ftl/backend/schema/strcase" - "github.com/TBD54566975/ftl/backend/common/rpc" + "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/ftl/go-runtime/encoding" ftlv1 "github.com/TBD54566975/ftl/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/protos/xyz/block/ftl/v1/ftlv1connect" diff --git a/go.mod b/go.mod index a8fd63c485..064a0bf2f2 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( github.com/gofrs/flock v0.8.1 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.6.0 - github.com/iancoleman/strcase v0.2.0 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgx/v5 v5.5.3 github.com/jellydator/ttlcache/v3 v3.1.1 diff --git a/go.sum b/go.sum index 2cc35e3665..457fc65a6f 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=