From b9e87503afbab100d47bbbf094ba6885f58bbf74 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 14 Dec 2024 08:52:37 -0800 Subject: [PATCH 01/20] initial commit for v0.1.0 --- examples/bare/cmd/app.go | 56 ++++ examples/core/cmd/app.go | 4 + examples/starter/cmd/api/main.go | 230 --------------- examples/starter/cmd/migration/main.go | 48 --- examples/starter/cmd/schemamigration/main.go | 37 --- examples/starter/cmd/seed/main.go | 15 - .../db/migrations/00000000000000_initial.yaml | 64 ---- .../20240909000000_modify_columns-1.down.yaml | 1 - .../20240909000000_modify_columns-1.up.yaml | 1 - .../20240910000000_modify_columns-2.down.yaml | 1 - .../20240910000000_modify_columns-2.up.yaml | 1 - examples/starter/db/seeders/development.go | 42 --- .../migrations/00000000000000_initital.yaml | 158 ---------- pkg/app/app.go | 114 -------- pkg/config/config.go | 25 -- pkg/context/context.go | 36 --- pkg/controller/handler.go | 149 ---------- pkg/controller/middleware.go | 157 ---------- pkg/core/app/app.go | 71 +++++ pkg/core/app/config.go | 21 ++ pkg/core/app/helper.go | 29 ++ .../command/migration/adapter/yaml/schema.go | 0 .../command/migration/adapter/yaml/yaml.go | 0 .../command/migration/constants/constants.go | 0 .../command/migration/helper/connstr.go | 0 .../command/migration/helper/helper.go | 0 pkg/{ => core}/command/migration/migration.go | 0 .../command/migration/reader/reader.go | 0 .../command/migration/reader/record.go | 0 .../command/migration/runner/runner.go | 0 .../command/migration/runner/yaml.go | 0 .../command/seeder/runner/helper.go | 0 .../command/seeder/runner/template.go | 0 pkg/{ => core}/command/seeder/seeder.go | 0 pkg/core/context/context.go | 17 ++ pkg/{controller => core/datasource}/.keep | 0 pkg/{ => core}/datasource/logging/logging.go | 0 .../datasource/orm/errors/errors.go | 0 pkg/{ => core}/datasource/orm/executor.go | 0 pkg/{ => core}/datasource/orm/orm.go | 0 pkg/{ => core}/datasource/orm/orm_test.go | 0 .../datasource/orm/validator/validator.go | 0 pkg/{ => core}/datasource/query/query.go | 0 pkg/{ => core}/db/db.go | 0 pkg/{ => core}/db/logger.go | 0 pkg/{ => core}/generator/generator.go | 0 pkg/core/middleware/middleware.go | 34 +++ .../middleware}/middleware_test.go | 24 +- pkg/core/request/query.go | 33 +++ pkg/core/request/request.go | 69 +++++ pkg/core/response/adapter.go | 57 ++++ pkg/core/response/factory.go | 17 ++ pkg/core/response/response.go | 62 ++++ pkg/{datasource => core/route}/.keep | 0 pkg/core/route/factory.go | 91 ++++++ pkg/core/route/group.go | 29 ++ pkg/core/route/handler.go | 72 +++++ pkg/core/route/params.go | 64 ++++ pkg/{ => core}/schema/collection.go | 0 pkg/{ => core}/schema/collection_test.go | 0 pkg/{ => core}/schema/field.go | 0 .../schema/internal/renderer/helper.go | 0 .../schema/internal/renderer/jsonapi.go | 0 .../schema/internal/renderer/schema.go | 0 .../schema/internal/renderer/shared.go | 0 .../fixtures/test_resource_serialize.json | 0 .../fixtures/test_resources_serialize.json | 0 .../schema/internal/schema/generated--like.go | 0 .../schema/internal/schema/generated--post.go | 0 .../internal/schema/generated--profile.go | 0 .../internal/schema/generated--shared.go | 0 .../schema/internal/schema/generated--user.go | 0 .../schema/internal/schema/jsonapi_test.go | 0 .../schema/internal/schema/orm_test.go | 0 .../schema/internal/schema/schema.go | 0 .../schema/internal/template/template.go | 0 .../schema/internal/valuetype/type.go | 0 pkg/{ => core}/schema/migration.go | 0 pkg/{ => core}/schema/parser.go | 0 pkg/{ => core}/schema/parser_test.go | 0 pkg/{ => core}/schema/schema.go | 0 pkg/http/request/request.go | 70 ----- pkg/http/response/adapter/jsonapi.go | 105 ------- pkg/http/response/adapter/jsonapi_test.go | 274 ------------------ pkg/http/response/adapter/raw.go | 29 -- pkg/http/response/response.go | 182 ------------ pkg/payload/fixtures/.env.test | 3 - pkg/{ => toolkit}/auth/auth.go | 0 pkg/{ => toolkit}/auth/error.go | 0 pkg/{ => toolkit}/auth/helper.go | 0 pkg/{ => toolkit}/auth/validate.go | 0 pkg/{ => toolkit}/errors/errors.go | 0 pkg/{ => toolkit}/errors/errors_test.go | 0 .../client => toolkit/httpclient}/client.go | 0 pkg/{ => toolkit}/logger/logger.go | 0 pkg/toolkit/middleware/middleware.go | 127 ++++++++ pkg/toolkit/middleware/middleware_test.go | 1 + pkg/{ => toolkit}/payload/loader.go | 0 pkg/{ => toolkit}/payload/loader_test.go | 0 pkg/{ => toolkit}/payload/payload.go | 0 pkg/{ => toolkit}/presenter/jsonapi/error.go | 0 pkg/{ => toolkit}/presenter/jsonapi/helper.go | 0 .../presenter/jsonapi/jsonapi.go | 0 .../presenter/jsonapi/stringify.go | 0 pkg/{ => toolkit}/presenter/view/view.go | 0 pkg/{ => toolkit}/strings/strings.go | 0 pkg/{ => toolkit}/strings/strings_test.go | 0 .../testing/cleaner/adapter/pq.go | 0 pkg/{ => toolkit}/testing/cleaner/cleaner.go | 0 pkg/{ => toolkit}/testing/table.go | 0 pkg/{ => toolkit}/util/util.go | 0 pkg/{ => toolkit}/util/util_test.go | 0 112 files changed, 861 insertions(+), 1759 deletions(-) create mode 100644 examples/bare/cmd/app.go create mode 100644 examples/core/cmd/app.go delete mode 100644 examples/starter/cmd/api/main.go delete mode 100644 examples/starter/cmd/migration/main.go delete mode 100644 examples/starter/cmd/schemamigration/main.go delete mode 100644 examples/starter/cmd/seed/main.go delete mode 100644 examples/starter/db/migrations/00000000000000_initial.yaml delete mode 100644 examples/starter/db/migrations/20240909000000_modify_columns-1.down.yaml delete mode 100644 examples/starter/db/migrations/20240909000000_modify_columns-1.up.yaml delete mode 100644 examples/starter/db/migrations/20240910000000_modify_columns-2.down.yaml delete mode 100644 examples/starter/db/migrations/20240910000000_modify_columns-2.up.yaml delete mode 100644 examples/starter/db/seeders/development.go delete mode 100644 examples/starter/db/v2/migrations/00000000000000_initital.yaml delete mode 100644 pkg/app/app.go delete mode 100644 pkg/config/config.go delete mode 100644 pkg/context/context.go delete mode 100644 pkg/controller/handler.go delete mode 100644 pkg/controller/middleware.go create mode 100644 pkg/core/app/app.go create mode 100644 pkg/core/app/config.go create mode 100644 pkg/core/app/helper.go rename pkg/{ => core}/command/migration/adapter/yaml/schema.go (100%) rename pkg/{ => core}/command/migration/adapter/yaml/yaml.go (100%) rename pkg/{ => core}/command/migration/constants/constants.go (100%) rename pkg/{ => core}/command/migration/helper/connstr.go (100%) rename pkg/{ => core}/command/migration/helper/helper.go (100%) rename pkg/{ => core}/command/migration/migration.go (100%) rename pkg/{ => core}/command/migration/reader/reader.go (100%) rename pkg/{ => core}/command/migration/reader/record.go (100%) rename pkg/{ => core}/command/migration/runner/runner.go (100%) rename pkg/{ => core}/command/migration/runner/yaml.go (100%) rename pkg/{ => core}/command/seeder/runner/helper.go (100%) rename pkg/{ => core}/command/seeder/runner/template.go (100%) rename pkg/{ => core}/command/seeder/seeder.go (100%) create mode 100644 pkg/core/context/context.go rename pkg/{controller => core/datasource}/.keep (100%) rename pkg/{ => core}/datasource/logging/logging.go (100%) rename pkg/{ => core}/datasource/orm/errors/errors.go (100%) rename pkg/{ => core}/datasource/orm/executor.go (100%) rename pkg/{ => core}/datasource/orm/orm.go (100%) rename pkg/{ => core}/datasource/orm/orm_test.go (100%) rename pkg/{ => core}/datasource/orm/validator/validator.go (100%) rename pkg/{ => core}/datasource/query/query.go (100%) rename pkg/{ => core}/db/db.go (100%) rename pkg/{ => core}/db/logger.go (100%) rename pkg/{ => core}/generator/generator.go (100%) create mode 100644 pkg/core/middleware/middleware.go rename pkg/{controller => core/middleware}/middleware_test.go (59%) create mode 100644 pkg/core/request/query.go create mode 100644 pkg/core/request/request.go create mode 100644 pkg/core/response/adapter.go create mode 100644 pkg/core/response/factory.go create mode 100644 pkg/core/response/response.go rename pkg/{datasource => core/route}/.keep (100%) create mode 100644 pkg/core/route/factory.go create mode 100644 pkg/core/route/group.go create mode 100644 pkg/core/route/handler.go create mode 100644 pkg/core/route/params.go rename pkg/{ => core}/schema/collection.go (100%) rename pkg/{ => core}/schema/collection_test.go (100%) rename pkg/{ => core}/schema/field.go (100%) rename pkg/{ => core}/schema/internal/renderer/helper.go (100%) rename pkg/{ => core}/schema/internal/renderer/jsonapi.go (100%) rename pkg/{ => core}/schema/internal/renderer/schema.go (100%) rename pkg/{ => core}/schema/internal/renderer/shared.go (100%) rename pkg/{ => core}/schema/internal/schema/fixtures/test_resource_serialize.json (100%) rename pkg/{ => core}/schema/internal/schema/fixtures/test_resources_serialize.json (100%) rename pkg/{ => core}/schema/internal/schema/generated--like.go (100%) rename pkg/{ => core}/schema/internal/schema/generated--post.go (100%) rename pkg/{ => core}/schema/internal/schema/generated--profile.go (100%) rename pkg/{ => core}/schema/internal/schema/generated--shared.go (100%) rename pkg/{ => core}/schema/internal/schema/generated--user.go (100%) rename pkg/{ => core}/schema/internal/schema/jsonapi_test.go (100%) rename pkg/{ => core}/schema/internal/schema/orm_test.go (100%) rename pkg/{ => core}/schema/internal/schema/schema.go (100%) rename pkg/{ => core}/schema/internal/template/template.go (100%) rename pkg/{ => core}/schema/internal/valuetype/type.go (100%) rename pkg/{ => core}/schema/migration.go (100%) rename pkg/{ => core}/schema/parser.go (100%) rename pkg/{ => core}/schema/parser_test.go (100%) rename pkg/{ => core}/schema/schema.go (100%) delete mode 100644 pkg/http/request/request.go delete mode 100644 pkg/http/response/adapter/jsonapi.go delete mode 100644 pkg/http/response/adapter/jsonapi_test.go delete mode 100644 pkg/http/response/adapter/raw.go delete mode 100644 pkg/http/response/response.go delete mode 100644 pkg/payload/fixtures/.env.test rename pkg/{ => toolkit}/auth/auth.go (100%) rename pkg/{ => toolkit}/auth/error.go (100%) rename pkg/{ => toolkit}/auth/helper.go (100%) rename pkg/{ => toolkit}/auth/validate.go (100%) rename pkg/{ => toolkit}/errors/errors.go (100%) rename pkg/{ => toolkit}/errors/errors_test.go (100%) rename pkg/{http/client => toolkit/httpclient}/client.go (100%) rename pkg/{ => toolkit}/logger/logger.go (100%) create mode 100644 pkg/toolkit/middleware/middleware.go create mode 100644 pkg/toolkit/middleware/middleware_test.go rename pkg/{ => toolkit}/payload/loader.go (100%) rename pkg/{ => toolkit}/payload/loader_test.go (100%) rename pkg/{ => toolkit}/payload/payload.go (100%) rename pkg/{ => toolkit}/presenter/jsonapi/error.go (100%) rename pkg/{ => toolkit}/presenter/jsonapi/helper.go (100%) rename pkg/{ => toolkit}/presenter/jsonapi/jsonapi.go (100%) rename pkg/{ => toolkit}/presenter/jsonapi/stringify.go (100%) rename pkg/{ => toolkit}/presenter/view/view.go (100%) rename pkg/{ => toolkit}/strings/strings.go (100%) rename pkg/{ => toolkit}/strings/strings_test.go (100%) rename pkg/{ => toolkit}/testing/cleaner/adapter/pq.go (100%) rename pkg/{ => toolkit}/testing/cleaner/cleaner.go (100%) rename pkg/{ => toolkit}/testing/table.go (100%) rename pkg/{ => toolkit}/util/util.go (100%) rename pkg/{ => toolkit}/util/util_test.go (100%) diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go new file mode 100644 index 0000000..1c359b7 --- /dev/null +++ b/examples/bare/cmd/app.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "log" + "net/http" + + "github.com/version-1/gooo/pkg/core/app" + "github.com/version-1/gooo/pkg/core/request" + "github.com/version-1/gooo/pkg/core/response" + "github.com/version-1/gooo/pkg/core/route" + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +func main() { + cfg := &app.Config{} + cfg.SetLogger(logger.DefaultLogger) + + server := &app.App{ + Addr: ":8080", + Config: cfg, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + cfg.Logger().Errorf("Error: %v", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + }, + } + users := route.GroupHandler{ + Path: "/users", + Handlers: []route.HandlerInterface{ + route.JSON[request.Void, any]().Get("", func(res *response.Response[any], req *request.Request[request.Void]) { + res.Render(map[string]string{"message": "ok"}) + }), + route.JSON[any, any]().Post("", func(res *response.Response[any], req *request.Request[any]) { + res.Render(map[string]string{"message": "ok"}) + }), + route.JSON[request.Void, any]().Get(":id", func(res *response.Response[any], req *request.Request[request.Void]) { + res.Render(map[string]string{"message": "ok"}) + }), + route.JSON[request.Void, any]().Patch(":id", func(res *response.Response[any], req *request.Request[request.Void]) { + res.Render(map[string]string{"message": "ok"}) + }), + route.JSON[request.Void, any]().Delete(":id", func(res *response.Response[any], req *request.Request[request.Void]) { + res.Render(map[string]string{"message": "ok"}) + }), + }, + } + + handlers := users.List() + app.WithDefaultMiddlewares(server, handlers) + + ctx := context.Background() + if err := server.Run(ctx); err != nil { + log.Fatalf("failed to run app: %s", err) + } +} diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go new file mode 100644 index 0000000..da29a2c --- /dev/null +++ b/examples/core/cmd/app.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/examples/starter/cmd/api/main.go b/examples/starter/cmd/api/main.go deleted file mode 100644 index b268eb4..0000000 --- a/examples/starter/cmd/api/main.go +++ /dev/null @@ -1,230 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net/http" - "os" - "text/tabwriter" - "time" - - "github.com/version-1/gooo/pkg/app" - "github.com/version-1/gooo/pkg/config" - "github.com/version-1/gooo/pkg/controller" - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" - "github.com/version-1/gooo/pkg/logger" - "github.com/version-1/gooo/pkg/presenter/jsonapi" -) - -type Dummy struct { - ID string `json:"-"` - String string `json:"string"` - Number int `json:"number"` - Flag bool `json:"flag"` - Time time.Time `json:"time"` -} - -func (e Dummy) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - r := jsonapi.Resource{ - ID: e.ID, - Type: "dummy", - Attributes: jsonapi.NewAttributes(e), - } - - return r, jsonapi.Resources{} -} - -type DummyError struct{} - -func (e DummyError) Error() string { - return "overridden error" -} - -func (e DummyError) Code() string { - return "overridden_error" -} - -func (e DummyError) Title() string { - return "Overrridden Error" -} - -func main() { - ping := controller.Handler{ - Path: "/ping", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - w.JSON(map[string]string{"message": "pong"}) - }, - } - - testing := controller.GroupHandler{ - Path: "/testing", - Handlers: []controller.Handler{ - { - Path: "/json", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - w.JSON(map[string]string{"message": "ok"}) - }, - }, - { - Path: "/render", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - data := Dummy{ - ID: "1", - String: "Hello, World!", - Number: 42, - Flag: true, - Time: time.Now(), - } - if err := w.Render(data); err != nil { - fmt.Printf("error: %+v\n", err) - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/render_many", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - data := []jsonapi.Resourcer{ - Dummy{ - ID: "1", - String: "Hello, World!", - Number: 42, - Flag: true, - Time: time.Now(), - }, - Dummy{ - ID: "2", - String: "Hello, World!", - Number: 42, - Flag: true, - Time: time.Now(), - }, - Dummy{ - ID: "3", - String: "Hello, World!", - Number: 42, - Flag: true, - Time: time.Now(), - }, - } - if err := w.Render(data); err != nil { - fmt.Printf("error: %+v\n", err) - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/render_error", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - if err := w.RenderError(fmt.Errorf("error")); err != nil { - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/internal_server_error", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - w.InternalServerErrorWith(DummyError{}) - }, - }, - { - Path: "/bad_request", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - if err := w.BadRequestWith(DummyError{}); err != nil { - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/unauthorized", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - if err := w.UnauthorizedWith(DummyError{}); err != nil { - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/forbidden", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - if err := w.ForbiddenWith(DummyError{}); err != nil { - w.InternalServerErrorWith(err) - } - }, - }, - { - Path: "/not_found", - Method: http.MethodGet, - Handler: func(w *response.Response, r *request.Request) { - if err := w.NotFoundWith(DummyError{}); err != nil { - w.InternalServerErrorWith(err) - } - }, - }, - }, - } - - users := controller.GroupHandler{ - Path: "/users", - Handlers: []controller.Handler{ - { - Path: "/", - Method: http.MethodGet, - }, - { - Path: "/", - Method: http.MethodPost, - }, - { - Path: "/:id", - Method: http.MethodPatch, - }, - { - Path: "/:id", - Method: http.MethodGet, - }, - { - Path: "/:id", - Method: http.MethodDelete, - }, - }, - }.List() - - apiRoot := controller.GroupHandler{ - Path: "/api/v1", - } - apiRoot.Add(users...) - apiRoot.Add(ping) - apiRoot.Add(testing.List()...) - - cfg := &config.App{ - Logger: logger.DefaultLogger, - DefaultResponseRenderer: config.JSONAPIRenderer, - } - - s := app.Server{ - Addr: ":8080", - Config: cfg, - } - s.RegisterHandlers(apiRoot.List()...) - app.WithDefaultMiddlewares(&s) - - w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - fmt.Fprint(w, logger.DefaultLogger.SInfof("Path\t|\tMethod")) - s.WalkThrough(func(h controller.Handler) { - fmt.Fprint(w, logger.DefaultLogger.SInfof("%s\t|\t%s\t", h.Path, h.Method)) - }) - fmt.Fprint(w, logger.DefaultLogger.SInfof("")) - w.Flush() - - s.Run(context.Background()) -} diff --git a/examples/starter/cmd/migration/main.go b/examples/starter/cmd/migration/main.go deleted file mode 100644 index 33b63ed..0000000 --- a/examples/starter/cmd/migration/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/command/migration" - "github.com/version-1/gooo/pkg/command/migration/runner" -) - -type connector struct{} - -func (c connector) Connect() (*sqlx.DB, error) { - return sqlx.Connect("postgres", os.Getenv("DATABASE_URL")) -} - -func main() { - conn := connector{} - ctx := context.Background() - m, err := runner.NewYaml(os.Getenv("MIGRATION_PATH")) - if err != nil { - panic(err) - } - - c, err := migration.NewWith(conn, m, nil) - if err != nil { - panic(err) - } - - if len(os.Args) == 1 { - fmt.Println("command is required. [up|down|create|drop|generate]") - os.Exit(1) - return - } - - cmd := os.Args[1] - - args := []string{} - if len(os.Args) >= 3 { - args = os.Args[2:] - } - - if err = c.Exec(ctx, cmd, args...); err != nil { - panic(err) - } -} diff --git a/examples/starter/cmd/schemamigration/main.go b/examples/starter/cmd/schemamigration/main.go deleted file mode 100644 index e05bb43..0000000 --- a/examples/starter/cmd/schemamigration/main.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/version-1/gooo/pkg/schema" -) - -func main() { - p := schema.NewParser() - list, err := p.Parse("./pkg/schema/internal/schema/schema.go") - if err != nil { - panic(err) - } - - s := schema.SchemaCollection{ - URL: "github.com/version-1/gooo", - Dir: "internal/schema", - Package: "schema", - Schemas: list, - } - - m := schema.NewMigration(s, schema.MigrationConfig{}) - os, err := m.OriginSchema() - if err != nil { - panic(err) - } - - filename := fmt.Sprintf("%s_initital.yaml", strings.Repeat("0", 14)) - path := fmt.Sprintf("examples/starter/db/v2/migrations/%s", filename) - fmt.Printf("Writing to %s\n", path) - if err := os.Write(path); err != nil { - fmt.Printf("Error: %+v\n", err) - panic(err) - } -} diff --git a/examples/starter/cmd/seed/main.go b/examples/starter/cmd/seed/main.go deleted file mode 100644 index 2bbac54..0000000 --- a/examples/starter/cmd/seed/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "os" - - "github.com/version-1/gooo/examples/starter/db/seeders" - "github.com/version-1/gooo/pkg/command/seeder" -) - -func main() { - seed := seeders.NewDevelopmentSeed(os.Getenv("DATABASE_URL")) - - ex := seeder.New(seed) - ex.Run() -} diff --git a/examples/starter/db/migrations/00000000000000_initial.yaml b/examples/starter/db/migrations/00000000000000_initial.yaml deleted file mode 100644 index f139cb0..0000000 --- a/examples/starter/db/migrations/00000000000000_initial.yaml +++ /dev/null @@ -1,64 +0,0 @@ -tables: - - name: migration_test_users - columns: - - name: id - type: int - primary_key: true - - name: name - type: varchar - - name: email - type: varchar - - name: created_at - type: timestamp - default: "CURRENT_TIMESTAMP" - - name: updated_at - type: timestamp - default: "CURRENT_TIMESTAMP" - indexes: - - name: unique_name_email - columns: [name, email] - unique: true - - name: migration_test_posts - columns: - - name: id - type: int - primary_key: true - - name: user_id - type: int - - name: title - type: varchar - - name: body - type: text - - name: created_at - type: timestamp - default: "CURRENT_TIMESTAMP" - - name: updated_at - type: timestamp - default: "CURRENT_TIMESTAMP" - indexes: - - columns: [user_id] - - name: user_ref_idx - columns: [user_id] - foreign_key: - table: migration_test_users - column: id - - name: migration_test_comments - columns: - - name: id - type: int - primary_key: true - - name: post_id - type: int - - name: user_id - type: int - - name: body - type: text - - name: created_at - type: timestamp - default: "CURRENT_TIMESTAMP" - - name: updated_at - type: timestamp - default: "CURRENT_TIMESTAMP" - indexes: - - columns: [post_id] - - columns: [user_id] diff --git a/examples/starter/db/migrations/20240909000000_modify_columns-1.down.yaml b/examples/starter/db/migrations/20240909000000_modify_columns-1.down.yaml deleted file mode 100644 index 4952ec2..0000000 --- a/examples/starter/db/migrations/20240909000000_modify_columns-1.down.yaml +++ /dev/null @@ -1 +0,0 @@ -query: "SELECT 1;" diff --git a/examples/starter/db/migrations/20240909000000_modify_columns-1.up.yaml b/examples/starter/db/migrations/20240909000000_modify_columns-1.up.yaml deleted file mode 100644 index 4952ec2..0000000 --- a/examples/starter/db/migrations/20240909000000_modify_columns-1.up.yaml +++ /dev/null @@ -1 +0,0 @@ -query: "SELECT 1;" diff --git a/examples/starter/db/migrations/20240910000000_modify_columns-2.down.yaml b/examples/starter/db/migrations/20240910000000_modify_columns-2.down.yaml deleted file mode 100644 index 4952ec2..0000000 --- a/examples/starter/db/migrations/20240910000000_modify_columns-2.down.yaml +++ /dev/null @@ -1 +0,0 @@ -query: "SELECT 1;" diff --git a/examples/starter/db/migrations/20240910000000_modify_columns-2.up.yaml b/examples/starter/db/migrations/20240910000000_modify_columns-2.up.yaml deleted file mode 100644 index 4952ec2..0000000 --- a/examples/starter/db/migrations/20240910000000_modify_columns-2.up.yaml +++ /dev/null @@ -1 +0,0 @@ -query: "SELECT 1;" diff --git a/examples/starter/db/seeders/development.go b/examples/starter/db/seeders/development.go deleted file mode 100644 index 7652974..0000000 --- a/examples/starter/db/seeders/development.go +++ /dev/null @@ -1,42 +0,0 @@ -package seeders - -import ( - "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/command/seeder" - "github.com/version-1/gooo/pkg/logger" -) - -type DevelopmentSeed struct { - connstr string -} - -func NewDevelopmentSeed(connstr string) DevelopmentSeed { - return DevelopmentSeed{ - connstr: connstr, - } -} - -func (s DevelopmentSeed) Connstr() string { - return s.connstr -} - -func (s DevelopmentSeed) Seeders() []seeder.Seeder { - return []seeder.Seeder{ - Seed_0001_User{}, - } -} - -func (S DevelopmentSeed) Logger() seeder.Logger { - return logger.DefaultLogger -} - -type Seed_0001_User struct{} - -func (s Seed_0001_User) Exec(tx *sqlx.Tx) error { - query := "INSERT INTO seeder_users (name, email) VALUES ('John Doe', 'john@example.com')" - if _, err := tx.Exec(query); err != nil { - return err - } - - return nil -} diff --git a/examples/starter/db/v2/migrations/00000000000000_initital.yaml b/examples/starter/db/v2/migrations/00000000000000_initital.yaml deleted file mode 100644 index 7f6dc66..0000000 --- a/examples/starter/db/v2/migrations/00000000000000_initital.yaml +++ /dev/null @@ -1,158 +0,0 @@ -tables: - - name: users - columns: - - name: id - type: INT - default: "" - allow_null: false - primary_key: true - - name: username - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: email - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: refresh_token - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: timezone - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: time_diff - type: INT - default: "" - allow_null: false - primary_key: false - - name: created_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - - name: updated_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - indexes: - - name: index_users_username - columns: - - username - unique: true - foreign_key: null - - name: posts - columns: - - name: id - type: INT - default: "" - allow_null: false - primary_key: true - - name: user_id - type: INT - default: "" - allow_null: false - primary_key: false - - name: title - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: body - type: text - default: "" - allow_null: false - primary_key: false - - name: created_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - - name: updated_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - indexes: - - name: index_posts_user_id - columns: - - user_id - unique: false - foreign_key: null - - name: profiles - columns: - - name: id - type: INT - default: "" - allow_null: false - primary_key: true - - name: user_id - type: INT - default: "" - allow_null: false - primary_key: false - - name: bio - type: text - default: "" - allow_null: false - primary_key: false - - name: created_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - - name: updated_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - indexes: - - name: index_profiles_user_id - columns: - - user_id - unique: false - foreign_key: null - - name: likes - columns: - - name: id - type: INT - default: "" - allow_null: false - primary_key: true - - name: likeable_id - type: INT - default: "" - allow_null: false - primary_key: false - - name: likeable_type - type: VARCHAR(255) - default: "" - allow_null: false - primary_key: false - - name: created_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - - name: updated_at - type: TIMESTAMP - default: "" - allow_null: false - primary_key: false - indexes: - - name: index_likes_likeable_id - columns: - - likeable_id - unique: false - foreign_key: null - - name: index_likes_likeable_type - columns: - - likeable_type - unique: false - foreign_key: null diff --git a/pkg/app/app.go b/pkg/app/app.go deleted file mode 100644 index 46f54bb..0000000 --- a/pkg/app/app.go +++ /dev/null @@ -1,114 +0,0 @@ -package app - -import ( - gocontext "context" - "net/http" - "time" - - "github.com/version-1/gooo/pkg/config" - "github.com/version-1/gooo/pkg/context" - "github.com/version-1/gooo/pkg/controller" - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" - "github.com/version-1/gooo/pkg/logger" -) - -type Server struct { - Addr string - Config *config.App - ErrorHandler func(w *response.Response, r *request.Request, e error) - Handlers []controller.Handler - Middlewares []controller.Middleware -} - -func (s *Server) SetLogger(l logger.Logger) { - s.Config.Logger = l -} - -func (s Server) Logger() logger.Logger { - return s.Config.GetLogger() -} - -func (s *Server) RegisterHandlers(h ...controller.Handler) { - s.Handlers = append(s.Handlers, h...) -} - -func (s *Server) RegisterMiddlewares(m ...controller.Middleware) { - s.Middlewares = append(s.Middlewares, m...) -} - -func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - rr := &request.Request{ - Request: r, - } - ww := response.New( - w, - response.Options{ - Adapter: string(s.Config.DefaultResponseRenderer), - }, - ) - - for _, m := range s.Middlewares { - if m.If(rr) { - s.withRecover(m.String(), ww, rr, func() { - if next := m.Do(ww, rr); !next { - return - } - }) - } - } -} - -func WithDefaultMiddlewares(s *Server) { - s.RegisterMiddlewares( - controller.WithContext( - func(r *request.Request) *request.Request { - ctx := r.Context() - ctx = context.WithAppConfig(ctx, s.Config) - - return r.WithContext(ctx) - }, - ), - controller.RequestLogger(s.Logger()), - controller.RequestBodyLogger(s.Logger()), - controller.RequestHandler(s.Handlers), - controller.ResponseLogger(s.Logger()), - ) -} - -func (s Server) Run(ctx gocontext.Context) { - hs := &http.Server{ - Addr: s.Addr, - Handler: s, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - - if len(s.Handlers) == 0 { - panic("No handlers registered") - } - defer hs.Shutdown(ctx) - - s.Logger().Infof("Server is running on %s", s.Addr) - hs.ListenAndServe() -} - -func (s Server) WalkThrough(cb func(h controller.Handler)) { - for _, h := range s.Handlers { - cb(h) - } -} - -func (s Server) withRecover(spot string, w *response.Response, r *request.Request, fn func()) { - defer func() { - if e := recover(); e != nil { - s.Logger().Errorf("Caught panic on %s", spot) - if err, ok := e.(error); ok { - s.ErrorHandler(w, r, err) - } - } - }() - - fn() -} diff --git a/pkg/config/config.go b/pkg/config/config.go deleted file mode 100644 index fa63153..0000000 --- a/pkg/config/config.go +++ /dev/null @@ -1,25 +0,0 @@ -package config - -import ( - "github.com/version-1/gooo/pkg/logger" -) - -type App struct { - Logger logger.Logger - DefaultResponseRenderer ResponseRenderer -} - -type ResponseRenderer string - -const ( - JSONAPIRenderer ResponseRenderer = "jsonapi" - RawRenderer ResponseRenderer = "raw" -) - -func (c App) GetLogger() logger.Logger { - if c.Logger == nil { - return logger.DefaultLogger - } - - return c.Logger -} diff --git a/pkg/context/context.go b/pkg/context/context.go deleted file mode 100644 index 56ac810..0000000 --- a/pkg/context/context.go +++ /dev/null @@ -1,36 +0,0 @@ -package context - -import ( - "context" - - "github.com/version-1/gooo/pkg/config" -) - -const ( - APP_CONFIG_KEY = "gooo:request:app_config" - USER_CONFIG_KEY = "gooo:request:user_config" -) - -func Get[T any](ctx context.Context, key string) T { - return ctx.Value(key).(T) -} - -func With[T any](ctx context.Context, key string, value T) context.Context { - return context.WithValue(ctx, key, value) -} - -func WithAppConfig(ctx context.Context, cfg *config.App) context.Context { - return With(ctx, APP_CONFIG_KEY, cfg) -} - -func AppConfig(ctx context.Context) *config.App { - return Get[*config.App](ctx, APP_CONFIG_KEY) -} - -func WithUserConfig[T any](ctx context.Context, u T) context.Context { - return With(ctx, USER_CONFIG_KEY, u) -} - -func UserConfig[T any](ctx context.Context) T { - return Get[T](ctx, USER_CONFIG_KEY) -} diff --git a/pkg/controller/handler.go b/pkg/controller/handler.go deleted file mode 100644 index a32aa26..0000000 --- a/pkg/controller/handler.go +++ /dev/null @@ -1,149 +0,0 @@ -package controller - -import ( - "fmt" - "path/filepath" - "strconv" - "strings" - - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" -) - -type BeforeHandlerFunc func(*response.Response, *request.Request) bool -type HandlerFunc func(*response.Response, *request.Request) - -type GroupHandler struct { - Path string - Handlers []Handler -} - -func (g *GroupHandler) Add(h ...Handler) { - g.Handlers = append(g.Handlers, h...) -} - -func (g GroupHandler) List() []Handler { - list := make([]Handler, len(g.Handlers)) - for i, h := range g.Handlers { - h.Path = filepath.Clean(g.Path + h.Path) - list[i] = h - } - - return list -} - -type Handler struct { - Path string - Method string - BeforeHandler *BeforeHandlerFunc - Handler HandlerFunc -} - -func (h Handler) String() string { - return fmt.Sprintf("Handler [%s] %s", h.Method, h.Path) -} - -func (h Handler) Match(r *request.Request) bool { - if r.Request.Method != h.Method { - return false - } - - if r.Request.URL.Path == h.Path { - return true - } - - parts := strings.Split(h.Path, "/") - targetParts := strings.Split(r.Request.URL.Path, "/") - if len(parts) < len(targetParts) { - return false - } - - for i, part := range parts { - if !strings.HasPrefix(part, ":") && part != targetParts[i] { - return false - } - } - - return false -} - -func (h Handler) Param(url string, key string) (string, bool) { - search := ":" + key - if !strings.Contains(h.Path, search) { - return "", false - } - - parts := strings.Split(h.Path, "/") - index := -1 - for i, part := range parts { - if part == search { - index = i - break - } - } - - if index == -1 { - return "", false - } - - targetParts := strings.Split(url, "/") - if len(targetParts) < index { - return "", false - } - - return targetParts[index], true -} - -func (h Handler) ParamInt(url string, key string) (int, bool) { - v, ok := h.Param(url, key) - if !ok { - return 0, false - } - - n, err := strconv.Atoi(v) - if err != nil { - return 0, false - } - - return n, true -} - -func Post(path string, handler HandlerFunc) Handler { - return Handler{ - Path: path, - Method: "POST", - Handler: handler, - } -} - -func Get(path string, handler HandlerFunc) Handler { - return Handler{ - Path: path, - Method: "GET", - Handler: handler, - } -} - -func Put(path string, handler HandlerFunc) Handler { - return Handler{ - Path: path, - Method: "PUT", - Handler: handler, - } -} - -func Patch(path string, handler HandlerFunc) Handler { - return Handler{ - Path: path, - Method: "PATCH", - Handler: handler, - } -} - -func Delete(path string, handler HandlerFunc) Handler { - return Handler{ - Path: path, - Method: "DELETE", - Handler: handler, - } -} diff --git a/pkg/controller/middleware.go b/pkg/controller/middleware.go deleted file mode 100644 index c4fc8f1..0000000 --- a/pkg/controller/middleware.go +++ /dev/null @@ -1,157 +0,0 @@ -package controller - -import ( - "bytes" - "fmt" - "io" - "net/http" - "strings" - - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" - "github.com/version-1/gooo/pkg/logger" -) - -type Middlewares []Middleware - -func (m *Middlewares) Append(mw ...Middleware) { - *m = append(*m, mw...) -} - -func (m *Middlewares) Insert(index int, mw Middleware) { - list := []Middleware{} - for i, it := range *m { - if i == index { - list = append(list, mw) - } - - list = append(list, it) - } - - *m = list -} - -func (m *Middlewares) Prepend(mw ...Middleware) { - list := mw - for _, it := range *m { - list = append(list, it) - } - *m = list -} - -type Middleware struct { - Name string - If func(*request.Request) bool - Do func(*response.Response, *request.Request) bool -} - -func (m Middleware) String() string { - return fmt.Sprintf("Middleware %s", m.Name) -} - -func Always(r *request.Request) bool { - return true -} - -func RequestLogger(logger logger.Logger) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - logger.Infof("%s %s", r.Request.Method, r.Request.URL.Path) - return true - }, - } -} - -func ResponseLogger(logger logger.Logger) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - logger.Infof("Status: %d", w.StatusCode()) - return true - }, - } -} - -func RequestBodyLogger(logger logger.Logger) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - b, err := io.ReadAll(r.Request.Body) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Internal server error")) - logger.Errorf("Error reading request body: %v", err) - return false - } - - io.Copy(w, io.MultiReader(bytes.NewReader(b), r.Request.Body)) - if len(b) > 0 { - logger.Infof("body: %s", b) - } - return true - }, - } -} - -func RequestHeaderLogger(logger logger.Logger) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - logger.Infof("HTTP Headers: ") - for k, v := range r.Request.Header { - logger.Infof("%s: %s", k, v) - } - return true - }, - } -} - -func CORS(origin, methods, headers []string) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - w.Header().Set("Access-Control-Allow-Origin", strings.Join(origin, ", ")) - w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ", ")) - w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ", ")) - return true - }, - } -} - -func WithContext(callbacks ...func(r *request.Request) *request.Request) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - for _, cb := range callbacks { - *r = *cb(r) - } - - return true - }, - } -} - -func RequestHandler(handlers []Handler) Middleware { - return Middleware{ - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - match := false - for _, handler := range handlers { - if handler.Match(r) { - if handler.BeforeHandler != nil { - (*handler.BeforeHandler)(w, r) - } - handler.Handler(w, r) - match = true - break - } - } - if !match { - w.NotFoundWith(fmt.Errorf("Not found endpoint: %s", r.Request.URL.Path)) - } - - return match - }, - } -} diff --git a/pkg/core/app/app.go b/pkg/core/app/app.go new file mode 100644 index 0000000..b63b3b4 --- /dev/null +++ b/pkg/core/app/app.go @@ -0,0 +1,71 @@ +package app + +import ( + gocontext "context" + "net/http" + "time" + + "github.com/version-1/gooo/pkg/core/middleware" + "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +type App struct { + Addr string + Config *Config + ErrorHandler func(w http.ResponseWriter, r *http.Request, e error) + Middlewares middleware.Middlewares +} + +func (s *App) SetLogger(l logger.Logger) { + s.Config.logger = l +} + +func (s App) Logger() logger.Logger { + return s.Config.Logger() +} + +func (s App) ServeHTTP(w http.ResponseWriter, r *http.Request) { + for _, m := range s.Middlewares { + if m.If(r) { + s.withRecover(m.String(), w, r, func() { + if next := m.Do(w, r); !next { + return + } + }) + } + } +} + +func (s App) Run(ctx gocontext.Context) error { + hs := &http.Server{ + Addr: s.Addr, + Handler: s, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + + defer hs.Shutdown(ctx) + + s.Logger().Infof("App is running on %s", s.Addr) + return hs.ListenAndServe() +} + +func (s App) withRecover(spot string, w http.ResponseWriter, r *http.Request, fn func()) { + defer func() { + if e := recover(); e != nil { + s.Logger().Errorf("Caught panic on %s", spot) + if err, ok := e.(error); ok { + s.ErrorHandler(w, r, err) + } + + if v, ok := e.(string); ok { + err := errors.New(v) + s.ErrorHandler(w, r, err) + } + } + }() + + fn() +} diff --git a/pkg/core/app/config.go b/pkg/core/app/config.go new file mode 100644 index 0000000..5d846c9 --- /dev/null +++ b/pkg/core/app/config.go @@ -0,0 +1,21 @@ +package app + +import ( + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +type Config struct { + logger logger.Logger +} + +func (c *Config) SetLogger(l logger.Logger) { + c.logger = l +} + +func (c Config) Logger() logger.Logger { + if c.logger == nil { + return logger.DefaultLogger + } + + return c.logger +} diff --git a/pkg/core/app/helper.go b/pkg/core/app/helper.go new file mode 100644 index 0000000..18d6df6 --- /dev/null +++ b/pkg/core/app/helper.go @@ -0,0 +1,29 @@ +package app + +import ( + "net/http" + + "github.com/version-1/gooo/pkg/core/context" + "github.com/version-1/gooo/pkg/core/middleware" + + helper "github.com/version-1/gooo/pkg/toolkit/middleware" +) + +func WithDefaultMiddlewares(a *App, handlers []helper.Handler) middleware.Middlewares { + a.Middlewares = middleware.Middlewares([]middleware.Middleware{ + helper.WithContext( + func(r *http.Request) *http.Request { + ctx := r.Context() + ctx = context.With(ctx, context.APP_CONFIG_KEY, a.Config) + + return r.WithContext(ctx) + }, + ), + helper.RequestLogger(a.Logger()), + helper.RequestBodyLogger(a.Logger()), + helper.RequestHandler(handlers), + helper.ResponseLogger(a.Logger()), + }) + + return a.Middlewares +} diff --git a/pkg/command/migration/adapter/yaml/schema.go b/pkg/core/command/migration/adapter/yaml/schema.go similarity index 100% rename from pkg/command/migration/adapter/yaml/schema.go rename to pkg/core/command/migration/adapter/yaml/schema.go diff --git a/pkg/command/migration/adapter/yaml/yaml.go b/pkg/core/command/migration/adapter/yaml/yaml.go similarity index 100% rename from pkg/command/migration/adapter/yaml/yaml.go rename to pkg/core/command/migration/adapter/yaml/yaml.go diff --git a/pkg/command/migration/constants/constants.go b/pkg/core/command/migration/constants/constants.go similarity index 100% rename from pkg/command/migration/constants/constants.go rename to pkg/core/command/migration/constants/constants.go diff --git a/pkg/command/migration/helper/connstr.go b/pkg/core/command/migration/helper/connstr.go similarity index 100% rename from pkg/command/migration/helper/connstr.go rename to pkg/core/command/migration/helper/connstr.go diff --git a/pkg/command/migration/helper/helper.go b/pkg/core/command/migration/helper/helper.go similarity index 100% rename from pkg/command/migration/helper/helper.go rename to pkg/core/command/migration/helper/helper.go diff --git a/pkg/command/migration/migration.go b/pkg/core/command/migration/migration.go similarity index 100% rename from pkg/command/migration/migration.go rename to pkg/core/command/migration/migration.go diff --git a/pkg/command/migration/reader/reader.go b/pkg/core/command/migration/reader/reader.go similarity index 100% rename from pkg/command/migration/reader/reader.go rename to pkg/core/command/migration/reader/reader.go diff --git a/pkg/command/migration/reader/record.go b/pkg/core/command/migration/reader/record.go similarity index 100% rename from pkg/command/migration/reader/record.go rename to pkg/core/command/migration/reader/record.go diff --git a/pkg/command/migration/runner/runner.go b/pkg/core/command/migration/runner/runner.go similarity index 100% rename from pkg/command/migration/runner/runner.go rename to pkg/core/command/migration/runner/runner.go diff --git a/pkg/command/migration/runner/yaml.go b/pkg/core/command/migration/runner/yaml.go similarity index 100% rename from pkg/command/migration/runner/yaml.go rename to pkg/core/command/migration/runner/yaml.go diff --git a/pkg/command/seeder/runner/helper.go b/pkg/core/command/seeder/runner/helper.go similarity index 100% rename from pkg/command/seeder/runner/helper.go rename to pkg/core/command/seeder/runner/helper.go diff --git a/pkg/command/seeder/runner/template.go b/pkg/core/command/seeder/runner/template.go similarity index 100% rename from pkg/command/seeder/runner/template.go rename to pkg/core/command/seeder/runner/template.go diff --git a/pkg/command/seeder/seeder.go b/pkg/core/command/seeder/seeder.go similarity index 100% rename from pkg/command/seeder/seeder.go rename to pkg/core/command/seeder/seeder.go diff --git a/pkg/core/context/context.go b/pkg/core/context/context.go new file mode 100644 index 0000000..9fec123 --- /dev/null +++ b/pkg/core/context/context.go @@ -0,0 +1,17 @@ +package context + +import ( + "context" +) + +const ( + APP_CONFIG_KEY = "gooo:request:app_config" +) + +func Get[T any](ctx context.Context, key string) T { + return ctx.Value(key).(T) +} + +func With[T any](ctx context.Context, key string, value T) context.Context { + return context.WithValue(ctx, key, value) +} diff --git a/pkg/controller/.keep b/pkg/core/datasource/.keep similarity index 100% rename from pkg/controller/.keep rename to pkg/core/datasource/.keep diff --git a/pkg/datasource/logging/logging.go b/pkg/core/datasource/logging/logging.go similarity index 100% rename from pkg/datasource/logging/logging.go rename to pkg/core/datasource/logging/logging.go diff --git a/pkg/datasource/orm/errors/errors.go b/pkg/core/datasource/orm/errors/errors.go similarity index 100% rename from pkg/datasource/orm/errors/errors.go rename to pkg/core/datasource/orm/errors/errors.go diff --git a/pkg/datasource/orm/executor.go b/pkg/core/datasource/orm/executor.go similarity index 100% rename from pkg/datasource/orm/executor.go rename to pkg/core/datasource/orm/executor.go diff --git a/pkg/datasource/orm/orm.go b/pkg/core/datasource/orm/orm.go similarity index 100% rename from pkg/datasource/orm/orm.go rename to pkg/core/datasource/orm/orm.go diff --git a/pkg/datasource/orm/orm_test.go b/pkg/core/datasource/orm/orm_test.go similarity index 100% rename from pkg/datasource/orm/orm_test.go rename to pkg/core/datasource/orm/orm_test.go diff --git a/pkg/datasource/orm/validator/validator.go b/pkg/core/datasource/orm/validator/validator.go similarity index 100% rename from pkg/datasource/orm/validator/validator.go rename to pkg/core/datasource/orm/validator/validator.go diff --git a/pkg/datasource/query/query.go b/pkg/core/datasource/query/query.go similarity index 100% rename from pkg/datasource/query/query.go rename to pkg/core/datasource/query/query.go diff --git a/pkg/db/db.go b/pkg/core/db/db.go similarity index 100% rename from pkg/db/db.go rename to pkg/core/db/db.go diff --git a/pkg/db/logger.go b/pkg/core/db/logger.go similarity index 100% rename from pkg/db/logger.go rename to pkg/core/db/logger.go diff --git a/pkg/generator/generator.go b/pkg/core/generator/generator.go similarity index 100% rename from pkg/generator/generator.go rename to pkg/core/generator/generator.go diff --git a/pkg/core/middleware/middleware.go b/pkg/core/middleware/middleware.go new file mode 100644 index 0000000..cd5567a --- /dev/null +++ b/pkg/core/middleware/middleware.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "fmt" + "net/http" +) + +type Middlewares []Middleware + +func (m *Middlewares) Append(mw ...Middleware) { + *m = append(*m, mw...) +} + +func (m *Middlewares) Prepend(mw ...Middleware) { + list := mw + for _, it := range *m { + list = append(list, it) + } + *m = list +} + +type Middleware struct { + Name string + If func(*http.Request) bool + Do func(http.ResponseWriter, *http.Request) bool +} + +func (m Middleware) String() string { + return fmt.Sprintf("Middleware %s", m.Name) +} + +func Always(r *http.Request) bool { + return true +} diff --git a/pkg/controller/middleware_test.go b/pkg/core/middleware/middleware_test.go similarity index 59% rename from pkg/controller/middleware_test.go rename to pkg/core/middleware/middleware_test.go index e31c6ad..7d90318 100644 --- a/pkg/controller/middleware_test.go +++ b/pkg/core/middleware/middleware_test.go @@ -1,23 +1,22 @@ -package controller +package middleware import ( "fmt" + "net/http" "reflect" "testing" - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" + "github.com/version-1/gooo/pkg/core/request" ) func TestMiddleware(t *testing.T) { - mw := Middlewares{} output := []string{} mw.Append(Middleware{ Name: "mw1", If: Always, - Do: func(w *response.Response, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *request.Request) bool { output = append(output, "mw1") return true }, @@ -26,7 +25,7 @@ func TestMiddleware(t *testing.T) { mw.Append(Middleware{ Name: "mw2", If: Always, - Do: func(w *response.Response, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *request.Request) bool { output = append(output, "mw2") return true }, @@ -35,25 +34,16 @@ func TestMiddleware(t *testing.T) { mw.Append(Middleware{ Name: "mw3", If: Always, - Do: func(w *response.Response, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *request.Request) bool { output = append(output, "mw3") return true }, }) - mw.Insert(1, Middleware{ - Name: "mw4", - If: Always, - Do: func(w *response.Response, r *request.Request) bool { - output = append(output, "mw4") - return true - }, - }) - mw.Prepend(Middleware{ Name: "mw5", If: Always, - Do: func(w *response.Response, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *request.Request) bool { output = append(output, "mw5") return true }, diff --git a/pkg/core/request/query.go b/pkg/core/request/query.go new file mode 100644 index 0000000..0466ea3 --- /dev/null +++ b/pkg/core/request/query.go @@ -0,0 +1,33 @@ +package request + +import ( + "net/url" + "strconv" + + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +type Query struct { + url url.URL + logger logger.Logger +} + +func (q Query) GetString(key string) (string, bool) { + v := q.url.Query().Get(key) + return v, v != "" +} + +func (q Query) GetInt(key string) (int, bool) { + v := q.url.Query().Get(key) + if v == "" { + return 0, false + } + + i, err := strconv.Atoi(v) + if err != nil { + q.logger.Errorf("failed to convert query param %s to int: %s", key, err) + return 0, false + } + + return i, true +} diff --git a/pkg/core/request/request.go b/pkg/core/request/request.go new file mode 100644 index 0000000..559d52f --- /dev/null +++ b/pkg/core/request/request.go @@ -0,0 +1,69 @@ +package request + +import ( + gocontext "context" + "io" + "net/http" + + "github.com/version-1/gooo/pkg/core/context" + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +type Void struct{} + +type Params interface { + GetString(key string) (string, error) + GetInt(key string) (int, error) + GetBool(key string) (bool, error) +} + +type Request[I any] struct { + params Params + *http.Request + body *[]byte + query Query +} + +func New[I any](r *http.Request, p Params) *Request[I] { + return &Request[I]{ + Request: r, + } +} + +func (r *Request[I]) Body() (I, error) { + var res I + if r.body == nil { + b, err := io.ReadAll(r.Request.Body) + if err != nil { + r.Logger().Errorf("failed to read request body: %s", err) + return res, err + } + + r.body = &b + } + + return res, nil + +} + +type loggerGetter interface { + Logger() logger.Logger +} + +func (r Request[I]) Logger() logger.Logger { + cfg := context.Get[loggerGetter](r.Request.Context(), context.APP_CONFIG_KEY) + return cfg.Logger() +} + +func (r Request[I]) Params() Params { + return r.params +} + +func (r Request[I]) Query() Query { + return r.query +} + +func (r *Request[I]) WithContext(ctx gocontext.Context) *Request[I] { + r.Request = r.Request.WithContext(ctx) + return r +} diff --git a/pkg/core/response/adapter.go b/pkg/core/response/adapter.go new file mode 100644 index 0000000..555715f --- /dev/null +++ b/pkg/core/response/adapter.go @@ -0,0 +1,57 @@ +package response + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type JSONAdapter struct{} + +func (a JSONAdapter) Render(w http.ResponseWriter, payload any, status int) error { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(status) + return json.NewEncoder(w).Encode(payload) +} + +func (a JSONAdapter) Error(w http.ResponseWriter, err error, status int) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(status) + + _err := json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + if _err != nil { + panic(_err) + } +} + +type HTMLAdapter struct{} + +func (a HTMLAdapter) Render(w http.ResponseWriter, payload any, status int) error { + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(status) + + body, ok := payload.([]byte) + if !ok { + return fmt.Errorf("body must be []byte but got %T", payload) + } + _, err := w.Write(body) + return err +} + +func (a HTMLAdapter) Error(w http.ResponseWriter, err error, status int) { + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(status) + + body := []byte(fmt.Sprintf(` + + +

Error: %s

+

Status: %d

+ + + `, err, status)) + + if _, err := w.Write(body); err != nil { + panic(err) + } +} diff --git a/pkg/core/response/factory.go b/pkg/core/response/factory.go new file mode 100644 index 0000000..b088380 --- /dev/null +++ b/pkg/core/response/factory.go @@ -0,0 +1,17 @@ +package response + +import "net/http" + +func JSON[O any]() *Response[O] { + return &Response[O]{ + adapter: JSONAdapter{}, + status: http.StatusOK, + } +} + +func HTML[O any]() *Response[O] { + return &Response[O]{ + adapter: HTMLAdapter{}, + status: http.StatusOK, + } +} diff --git a/pkg/core/response/response.go b/pkg/core/response/response.go new file mode 100644 index 0000000..eb91fcb --- /dev/null +++ b/pkg/core/response/response.go @@ -0,0 +1,62 @@ +package response + +import ( + "net/http" +) + +type Adapter interface { + Render(w http.ResponseWriter, payload any, status int) error + Error(w http.ResponseWriter, err error, status int) +} + +type Void struct{} + +type Response[O any] struct { + http.ResponseWriter + status int + adapter Adapter +} + +func New[O any](w http.ResponseWriter, a Adapter) *Response[O] { + return &Response[O]{ + ResponseWriter: w, + status: http.StatusOK, + adapter: a, + } +} + +func (r Response[O]) Render(o O) { + err := r.adapter.Render(r.ResponseWriter, o, r.status) + if err != nil { + r.adapter.Error(r.ResponseWriter, err, http.StatusInternalServerError) + } +} + +func (r *Response[O]) WriteHeader(code int) { + r.ResponseWriter.WriteHeader(code) + r.status = code +} + +func (r Response[O]) renderError(w http.ResponseWriter, err error) { + r.adapter.Error(w, err, r.status) +} + +func (r Response[O]) InternalServerError(w http.ResponseWriter, err error) { + r.status = http.StatusInternalServerError + r.renderError(w, err) +} + +func (r Response[O]) NotFound(w http.ResponseWriter, err error) { + r.status = http.StatusNotFound + r.renderError(w, err) +} + +func (r Response[O]) BadRequest(w http.ResponseWriter, err error) { + r.status = http.StatusBadRequest + r.renderError(w, err) +} + +func (r Response[O]) UnprocessableEntity(w http.ResponseWriter, err error) { + r.status = http.StatusUnprocessableEntity + r.renderError(w, err) +} diff --git a/pkg/datasource/.keep b/pkg/core/route/.keep similarity index 100% rename from pkg/datasource/.keep rename to pkg/core/route/.keep diff --git a/pkg/core/route/factory.go b/pkg/core/route/factory.go new file mode 100644 index 0000000..805dace --- /dev/null +++ b/pkg/core/route/factory.go @@ -0,0 +1,91 @@ +package route + +import ( + "net/http" + + "github.com/version-1/gooo/pkg/core/response" +) + +func JSON[I, O any]() *Handler[I, O] { + return &Handler[I, O]{ + adapter: response.JSONAdapter{}, + } +} + +func HTML[I, O any]() *Handler[I, O] { + return &Handler[I, O]{ + adapter: response.HTMLAdapter{}, + } +} + +func (h *Handler[I, O]) Get(path string, handler HandlerFunc[I, O]) *Handler[I, O] { + h.Path = path + h.Method = http.MethodGet + h.handler = handler + + return h +} + +func (h *Handler[I, O]) Post(path string, handler HandlerFunc[I, O]) *Handler[I, O] { + h.Path = path + h.Method = http.MethodPost + h.handler = handler + + return h +} + +func (h *Handler[I, O]) Patch(path string, handler HandlerFunc[I, O]) *Handler[I, O] { + h.Path = path + h.Method = http.MethodPatch + h.handler = handler + + return h +} + +func (h *Handler[I, O]) Delete(path string, handler HandlerFunc[I, O]) *Handler[I, O] { + h.Path = path + h.Method = http.MethodDelete + h.handler = handler + + return h +} + +func Post[I, O any](path string, handler HandlerFunc[I, O]) *Handler[I, O] { + return &Handler[I, O]{ + Path: path, + Method: http.MethodPost, + handler: handler, + } +} + +func Get[I, O any](path string, handler HandlerFunc[I, O]) *Handler[I, O] { + return &Handler[I, O]{ + Path: path, + Method: http.MethodGet, + handler: handler, + } +} + +func Put[I, O any](path string, handler HandlerFunc[I, O]) *Handler[I, O] { + return &Handler[I, O]{ + Path: path, + Method: http.MethodPut, + handler: handler, + } +} + +func Patch[I, O any](path string, handler HandlerFunc[I, O]) *Handler[I, O] { + return &Handler[I, O]{ + Path: path, + Method: http.MethodPatch, + handler: handler, + } +} + +func Delete[I, O any](path string, handler HandlerFunc[I, O]) *Handler[I, O] { + return &Handler[I, O]{ + Path: path, + Method: http.MethodDelete, + handler: handler, + } +} diff --git a/pkg/core/route/group.go b/pkg/core/route/group.go new file mode 100644 index 0000000..d5eb2c0 --- /dev/null +++ b/pkg/core/route/group.go @@ -0,0 +1,29 @@ +package route + +import ( + "github.com/version-1/gooo/pkg/toolkit/middleware" +) + +type GroupHandler struct { + Path string + Handlers []HandlerInterface +} + +type HandlerInterface interface { + middleware.Handler + ShiftPath(string) +} + +func (g *GroupHandler) Add(h ...HandlerInterface) { + g.Handlers = append(g.Handlers, h...) +} + +func (g GroupHandler) List() []middleware.Handler { + list := make([]middleware.Handler, len(g.Handlers)) + for i, h := range g.Handlers { + h.ShiftPath(g.Path) + list[i] = h + } + + return list +} diff --git a/pkg/core/route/handler.go b/pkg/core/route/handler.go new file mode 100644 index 0000000..b2a9795 --- /dev/null +++ b/pkg/core/route/handler.go @@ -0,0 +1,72 @@ +package route + +import ( + "fmt" + "net/http" + "net/url" + "path/filepath" + "strings" + + "github.com/version-1/gooo/pkg/core/request" + "github.com/version-1/gooo/pkg/core/response" + "github.com/version-1/gooo/pkg/toolkit/middleware" +) + +type HandlerFunc[I, O any] func(*response.Response[O], *request.Request[I]) + +var _ middleware.Handler = Handler[any, any]{} + +type Handler[I, O any] struct { + Path string + Method string + handler HandlerFunc[I, O] + params *Params + adapter response.Adapter +} + +func (h *Handler[I, O]) ShiftPath(base string) { + h.Path = filepath.Clean(base + "/" + h.Path) +} + +func (h Handler[I, O]) String() string { + return fmt.Sprintf("Handler [%s] %s", h.Method, h.Path) +} + +func (h Handler[I, O]) Handler(res http.ResponseWriter, req *http.Request) { + p := h.Param(*req.URL) + customRequest := request.New[I](req, p) + customResponse := response.New[O](res, h.adapter) + h.handler(customResponse, customRequest) +} + +func (h Handler[I, O]) Match(r *http.Request) bool { + if r.Method != h.Method { + return false + } + + if r.URL.Path == h.Path { + return true + } + + parts := strings.Split(h.Path, "/") + targetParts := strings.Split(r.URL.Path, "/") + if len(parts) < len(targetParts) { + return false + } + + for i, part := range parts { + if !strings.HasPrefix(part, ":") && part != targetParts[i] { + return false + } + } + + return true +} + +func (h Handler[I, O]) Param(uri url.URL) *Params { + if h.params == nil { + p := parseParams(h.Path, uri.Path) + h.params = &p + } + return h.params +} diff --git a/pkg/core/route/params.go b/pkg/core/route/params.go new file mode 100644 index 0000000..b8052fa --- /dev/null +++ b/pkg/core/route/params.go @@ -0,0 +1,64 @@ +package route + +import ( + "fmt" + "strconv" + "strings" +) + +type Params struct { + m map[string]string +} + +func parseParams(matcher, path string) Params { + p := Params{m: make(map[string]string)} + + pathSegments := strings.Split(path, "/") + matcherSegments := strings.Split(matcher, "/") + for i, part := range matcherSegments { + if strings.HasPrefix(part, ":") { + if len(pathSegments) > i { + p.m[part] = pathSegments[i] + } + } + } + + return p +} + +func (p Params) GetBool(key string) (bool, error) { + v, err := p.GetString(key) + if err != nil { + return false, err + } + + b, err := strconv.ParseBool(v) + if err != nil { + return false, err + } + + return b, nil +} + +func (p Params) GetString(key string) (string, error) { + v, ok := p.m[key] + if !ok { + return "", fmt.Errorf("param %s not found", key) + } + + return v, nil +} + +func (p Params) GetInt(key string) (int, error) { + v, err := p.GetString(key) + if err != nil { + return 0, err + } + + n, err := strconv.Atoi(v) + if err != nil { + return 0, err + } + + return n, nil +} diff --git a/pkg/schema/collection.go b/pkg/core/schema/collection.go similarity index 100% rename from pkg/schema/collection.go rename to pkg/core/schema/collection.go diff --git a/pkg/schema/collection_test.go b/pkg/core/schema/collection_test.go similarity index 100% rename from pkg/schema/collection_test.go rename to pkg/core/schema/collection_test.go diff --git a/pkg/schema/field.go b/pkg/core/schema/field.go similarity index 100% rename from pkg/schema/field.go rename to pkg/core/schema/field.go diff --git a/pkg/schema/internal/renderer/helper.go b/pkg/core/schema/internal/renderer/helper.go similarity index 100% rename from pkg/schema/internal/renderer/helper.go rename to pkg/core/schema/internal/renderer/helper.go diff --git a/pkg/schema/internal/renderer/jsonapi.go b/pkg/core/schema/internal/renderer/jsonapi.go similarity index 100% rename from pkg/schema/internal/renderer/jsonapi.go rename to pkg/core/schema/internal/renderer/jsonapi.go diff --git a/pkg/schema/internal/renderer/schema.go b/pkg/core/schema/internal/renderer/schema.go similarity index 100% rename from pkg/schema/internal/renderer/schema.go rename to pkg/core/schema/internal/renderer/schema.go diff --git a/pkg/schema/internal/renderer/shared.go b/pkg/core/schema/internal/renderer/shared.go similarity index 100% rename from pkg/schema/internal/renderer/shared.go rename to pkg/core/schema/internal/renderer/shared.go diff --git a/pkg/schema/internal/schema/fixtures/test_resource_serialize.json b/pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json similarity index 100% rename from pkg/schema/internal/schema/fixtures/test_resource_serialize.json rename to pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json diff --git a/pkg/schema/internal/schema/fixtures/test_resources_serialize.json b/pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json similarity index 100% rename from pkg/schema/internal/schema/fixtures/test_resources_serialize.json rename to pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json diff --git a/pkg/schema/internal/schema/generated--like.go b/pkg/core/schema/internal/schema/generated--like.go similarity index 100% rename from pkg/schema/internal/schema/generated--like.go rename to pkg/core/schema/internal/schema/generated--like.go diff --git a/pkg/schema/internal/schema/generated--post.go b/pkg/core/schema/internal/schema/generated--post.go similarity index 100% rename from pkg/schema/internal/schema/generated--post.go rename to pkg/core/schema/internal/schema/generated--post.go diff --git a/pkg/schema/internal/schema/generated--profile.go b/pkg/core/schema/internal/schema/generated--profile.go similarity index 100% rename from pkg/schema/internal/schema/generated--profile.go rename to pkg/core/schema/internal/schema/generated--profile.go diff --git a/pkg/schema/internal/schema/generated--shared.go b/pkg/core/schema/internal/schema/generated--shared.go similarity index 100% rename from pkg/schema/internal/schema/generated--shared.go rename to pkg/core/schema/internal/schema/generated--shared.go diff --git a/pkg/schema/internal/schema/generated--user.go b/pkg/core/schema/internal/schema/generated--user.go similarity index 100% rename from pkg/schema/internal/schema/generated--user.go rename to pkg/core/schema/internal/schema/generated--user.go diff --git a/pkg/schema/internal/schema/jsonapi_test.go b/pkg/core/schema/internal/schema/jsonapi_test.go similarity index 100% rename from pkg/schema/internal/schema/jsonapi_test.go rename to pkg/core/schema/internal/schema/jsonapi_test.go diff --git a/pkg/schema/internal/schema/orm_test.go b/pkg/core/schema/internal/schema/orm_test.go similarity index 100% rename from pkg/schema/internal/schema/orm_test.go rename to pkg/core/schema/internal/schema/orm_test.go diff --git a/pkg/schema/internal/schema/schema.go b/pkg/core/schema/internal/schema/schema.go similarity index 100% rename from pkg/schema/internal/schema/schema.go rename to pkg/core/schema/internal/schema/schema.go diff --git a/pkg/schema/internal/template/template.go b/pkg/core/schema/internal/template/template.go similarity index 100% rename from pkg/schema/internal/template/template.go rename to pkg/core/schema/internal/template/template.go diff --git a/pkg/schema/internal/valuetype/type.go b/pkg/core/schema/internal/valuetype/type.go similarity index 100% rename from pkg/schema/internal/valuetype/type.go rename to pkg/core/schema/internal/valuetype/type.go diff --git a/pkg/schema/migration.go b/pkg/core/schema/migration.go similarity index 100% rename from pkg/schema/migration.go rename to pkg/core/schema/migration.go diff --git a/pkg/schema/parser.go b/pkg/core/schema/parser.go similarity index 100% rename from pkg/schema/parser.go rename to pkg/core/schema/parser.go diff --git a/pkg/schema/parser_test.go b/pkg/core/schema/parser_test.go similarity index 100% rename from pkg/schema/parser_test.go rename to pkg/core/schema/parser_test.go diff --git a/pkg/schema/schema.go b/pkg/core/schema/schema.go similarity index 100% rename from pkg/schema/schema.go rename to pkg/core/schema/schema.go diff --git a/pkg/http/request/request.go b/pkg/http/request/request.go deleted file mode 100644 index ef40d97..0000000 --- a/pkg/http/request/request.go +++ /dev/null @@ -1,70 +0,0 @@ -package request - -import ( - gocontext "context" - "encoding/json" - "io" - "net/http" - "strconv" - - "github.com/version-1/gooo/pkg/context" - "github.com/version-1/gooo/pkg/logger" -) - -type ParamParser interface { - Param(url string, key string) (string, bool) - ParamInt(url string, key string) (int, bool) -} - -type Request struct { - Handler ParamParser - *http.Request -} - -func MarshalBody[T json.Unmarshaler](r *Request, obj *T) error { - b, err := io.ReadAll(r.Request.Body) - if err != nil { - return err - } - defer r.Request.Body.Close() - - return json.Unmarshal(b, obj) -} - -func (r Request) Logger() logger.Logger { - cfg := context.AppConfig(r.Request.Context()) - return cfg.Logger -} - -func (r Request) Param(key string) (string, bool) { - return r.Handler.Param(r.Request.URL.Path, key) -} - -func (r Request) ParamInt(key string) (int, bool) { - return r.Handler.ParamInt(r.Request.URL.Path, key) -} - -func (r Request) Query(key string) (string, bool) { - v := r.Request.URL.Query().Get(key) - return v, v != "" -} - -func (r Request) QueryInt(key string) (int, bool) { - v := r.Request.URL.Query().Get(key) - if v == "" { - return 0, false - } - - i, err := strconv.Atoi(v) - if err != nil { - r.Logger().Errorf("failed to convert query param %s to int: %s", key, err) - return 0, false - } - - return i, true -} - -func (r *Request) WithContext(ctx gocontext.Context) *Request { - r.Request = r.Request.WithContext(ctx) - return r -} diff --git a/pkg/http/response/adapter/jsonapi.go b/pkg/http/response/adapter/jsonapi.go deleted file mode 100644 index cf70d55..0000000 --- a/pkg/http/response/adapter/jsonapi.go +++ /dev/null @@ -1,105 +0,0 @@ -package adapter - -import ( - "fmt" - - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" -) - -type JSONAPI struct { - meta jsonapi.Serializer -} - -type JSONAPIOption struct { - Meta jsonapi.Serializer -} - -type JSONAPIInvalidTypeError struct { - Payload any -} - -func (e JSONAPIInvalidTypeError) Error() string { - return fmt.Sprintf("Payload must implement jsonapi.Resourcer or []jsonapi.Resourcer. got: %T", e.Payload) -} - -func (a JSONAPI) ContentType() string { - return "application/vnd.api+json" -} - -func (a *JSONAPI) Render(payload any, options ...any) ([]byte, error) { - return resolve(payload, options...) -} - -func RenderMany[T jsonapi.Resourcer](list []T, options ...any) ([]byte, error) { - return resolve(list, options...) -} - -func (a *JSONAPI) RenderError(e error, options ...any) ([]byte, error) { - b, _, err := resolveError(e, options...) - return b, err -} - -func resolve(payload any, options ...any) ([]byte, error) { - var meta jsonapi.Serializer - for _, opt := range options { - switch t := opt.(type) { - case JSONAPIOption: - meta = t.Meta - case *JSONAPIOption: - meta = t.Meta - } - } - - switch v := payload.(type) { - case jsonapi.Resourcer: - data, includes := v.ToJSONAPIResource() - r, err := jsonapi.New(data, includes, meta) - if err != nil { - return []byte{}, err - } - - s, err := r.Serialize() - return []byte(s), err - case []jsonapi.Resourcer: - r, err := jsonapi.NewManyFrom(v, meta) - if err != nil { - return []byte{}, err - } - - s, err := r.Serialize() - return []byte(s), err - case jsonapi.Resourcers: - r, err := jsonapi.NewManyFrom(v, meta) - if err != nil { - return []byte{}, err - } - - s, err := r.Serialize() - return []byte(s), err - default: - return []byte{}, goooerrors.Wrap(JSONAPIInvalidTypeError{Payload: v}) - } -} - -func resolveError(e error, options ...any) ([]byte, []jsonapi.Error, error) { - switch v := e.(type) { - case jsonapi.Errors: - s, err := jsonapi.NewErrors(v).Serialize() - return []byte(s), v, err - case jsonapi.Error: - errors := jsonapi.Errors{v} - s, err := jsonapi.NewErrors(errors).Serialize() - return []byte(s), errors, err - case jsonapi.Errable: - obj := v.ToJSONAPIError() - errors := jsonapi.Errors{obj} - s, err := jsonapi.NewErrors(errors).Serialize() - return []byte(s), errors, err - default: - obj := jsonapi.NewErrorResponse(v).ToJSONAPIError() - errors := jsonapi.Errors{obj} - s, err := jsonapi.NewErrors(errors).Serialize() - return []byte(s), errors, err - } -} diff --git a/pkg/http/response/adapter/jsonapi_test.go b/pkg/http/response/adapter/jsonapi_test.go deleted file mode 100644 index 746c662..0000000 --- a/pkg/http/response/adapter/jsonapi_test.go +++ /dev/null @@ -1,274 +0,0 @@ -package adapter - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - "github.com/google/uuid" - "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" - goootesting "github.com/version-1/gooo/pkg/testing" -) - -type dummy struct { - ID string `json:"-"` - String string `json:"string"` - Number int `json:"number"` - Bool bool `json:"bool"` - Time time.Time `json:"time"` -} - -func (d dummy) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - return jsonapi.Resource{ - ID: d.ID, - Type: "dummy", - Attributes: jsonapi.NewAttributes(d), - }, jsonapi.Resources{} -} - -type meta struct { - Key string `json:"key"` -} - -func (m meta) JSONAPISerialize() (string, error) { - b, err := json.Marshal(m) - return string(b), err -} - -func TestJSONAPIContentType(t *testing.T) { - a := JSONAPI{} - expect := "application/vnd.api+json" - if a.ContentType() != expect { - t.Errorf("Expected content type to be %s, got %s", expect, a.ContentType()) - } -} - -func TestJSONAPIRender(t *testing.T) { - a := JSONAPI{} - id1 := uuid.MustParse("325fe993-420a-4e53-8687-1760f34e0697").String() - id2 := uuid.MustParse("e3a341b2-0400-4e80-97b9-b1aa0119018b").String() - id3 := uuid.MustParse("f513710d-a158-4cdb-914f-bb8aa11bd675").String() - now := time.Now() - - test := goootesting.NewTable([]goootesting.Record[[]byte, []byte]{ - { - Name: "Render with jsonapi.Resourcer", - Subject: func(t *testing.T) ([]byte, error) { - s, err := a.Render(dummy{ - ID: id1, - String: "string", - Number: 1, - Bool: true, - Time: now, - }) - if err != nil { - return []byte{}, err - } - - buffer := &bytes.Buffer{} - err = json.Compact(buffer, s) - return buffer.Bytes(), err - }, - Expect: func(t *testing.T) ([]byte, error) { - s := fmt.Sprintf(`{ "data": { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 1, "bool": true, "time": "%s" } } }`, id1, now.Format(time.RFC3339Nano)) - buffer := &bytes.Buffer{} - err := json.Compact(buffer, []byte(s)) - return buffer.Bytes(), err - }, - Assert: func(t *testing.T, r *goootesting.Record[[]byte, []byte]) bool { - e, err := r.Expect(t) - s, serr := r.Subject(t) - - if !reflect.DeepEqual(e, s) { - t.Errorf("Expected %s, got %s", e, s) - return false - } - - if serr != nil && err.Error() != serr.Error() { - t.Errorf("Expected %v, got %v", err, serr) - return false - } - return true - }, - }, - { - Name: "Render with []jsonapi.Resourcer", - Subject: func(t *testing.T) ([]byte, error) { - list := []jsonapi.Resourcer{ - dummy{ - ID: id1, - String: "string", - Number: 1, - Bool: true, - Time: now, - }, - dummy{ - ID: id2, - String: "string", - Number: 2, - Bool: true, - Time: now, - }, - dummy{ - ID: id3, - String: "string", - Number: 3, - Bool: true, - Time: now, - }, - } - s, err := a.Render(list) - if err != nil { - return []byte{}, err - } - - buffer := &bytes.Buffer{} - err = json.Compact(buffer, s) - return buffer.Bytes(), err - }, - Expect: func(t *testing.T) ([]byte, error) { - s := fmt.Sprintf(`{ - "data": [ - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 1, "bool": true, "time": "%s" } }, - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 2, "bool": true, "time": "%s" } }, - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 3, "bool": true, "time": "%s" } } - ] - }`, - id1, - now.Format(time.RFC3339Nano), - id2, - now.Format(time.RFC3339Nano), - id3, - now.Format(time.RFC3339Nano), - ) - - buffer := &bytes.Buffer{} - err := json.Compact(buffer, []byte(s)) - return buffer.Bytes(), err - }, - Assert: func(t *testing.T, r *goootesting.Record[[]byte, []byte]) bool { - e, err := r.Expect(t) - s, serr := r.Subject(t) - - if !reflect.DeepEqual(e, s) { - t.Errorf("Expected %s, got %s", e, s) - return false - } - - if serr != nil && err.Error() != serr.Error() { - t.Errorf("Expected %v, got %v", err, serr) - return false - } - return true - }, - }, - { - Name: "Render with []jsonapi.Resourcer and meta", - Subject: func(t *testing.T) ([]byte, error) { - list := []jsonapi.Resourcer{ - dummy{ - ID: id2, - String: "string", - Number: 1, - Bool: true, - Time: now, - }, - dummy{ - ID: id1, - String: "string", - Number: 2, - Bool: true, - Time: now, - }, - dummy{ - ID: id3, - String: "string", - Number: 3, - Bool: true, - Time: now, - }, - } - - option := JSONAPIOption{ - Meta: meta{ - Key: "value", - }, - } - s, err := a.Render(list, option) - if err != nil { - return []byte{}, err - } - - buffer := &bytes.Buffer{} - err = json.Compact(buffer, s) - return buffer.Bytes(), err - }, - Expect: func(t *testing.T) ([]byte, error) { - s := fmt.Sprintf(`{ - "data": [ - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 1, "bool": true, "time": "%s" } }, - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 2, "bool": true, "time": "%s" } }, - { "id": "%s", "type": "dummy", "attributes": { "string": "string", "number": 3, "bool": true, "time": "%s" } } - ], - "meta": { "key": "value" } - }`, - id2, - now.Format(time.RFC3339Nano), - id1, - now.Format(time.RFC3339Nano), - id3, - now.Format(time.RFC3339Nano), - ) - - buffer := &bytes.Buffer{} - err := json.Compact(buffer, []byte(s)) - return buffer.Bytes(), err - }, - Assert: func(t *testing.T, r *goootesting.Record[[]byte, []byte]) bool { - e, err := r.Expect(t) - s, serr := r.Subject(t) - - if !reflect.DeepEqual(e, s) { - t.Errorf("Expected %s, got %s", e, s) - return false - } - - if serr != nil && err.Error() != serr.Error() { - t.Errorf("Expected %v, got %v", err, serr) - return false - } - return true - }, - }, - { - Name: "Render with invalid type", - Subject: func(t *testing.T) ([]byte, error) { - return a.Render("hoge") - }, - Expect: func(t *testing.T) ([]byte, error) { - return []byte{}, errors.Wrap(JSONAPIInvalidTypeError{"hoge"}) - }, - Assert: func(t *testing.T, r *goootesting.Record[[]byte, []byte]) bool { - e, err := r.Expect(t) - s, serr := r.Subject(t) - - if !reflect.DeepEqual(e, s) { - t.Errorf("Expected %v, got %v", e, err) - return false - } - - if err.Error() != serr.Error() { - t.Errorf("Expected %v, got %v", err, serr) - return false - } - return true - }, - }, - }) - - test.Run(t) -} diff --git a/pkg/http/response/adapter/raw.go b/pkg/http/response/adapter/raw.go deleted file mode 100644 index e3acde7..0000000 --- a/pkg/http/response/adapter/raw.go +++ /dev/null @@ -1,29 +0,0 @@ -package adapter - -import ( - "bytes" - "encoding/json" - "net/http" -) - -type Raw struct { - w http.ResponseWriter -} - -func (a Raw) ContentType() string { - return "text/plain" -} - -func (a Raw) Render(payload any, options ...any) ([]byte, error) { - buf := new(bytes.Buffer) - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return []byte{}, err - } - - return buf.Bytes(), nil -} - -func (a Raw) RenderError(e error, options ...any) ([]byte, error) { - return a.Render(e.Error(), options...) -} diff --git a/pkg/http/response/response.go b/pkg/http/response/response.go deleted file mode 100644 index 42fb068..0000000 --- a/pkg/http/response/response.go +++ /dev/null @@ -1,182 +0,0 @@ -package response - -import ( - "encoding/json" - "net/http" - - "github.com/version-1/gooo/pkg/http/response/adapter" - "github.com/version-1/gooo/pkg/logger" -) - -var _ http.ResponseWriter = &Response{} - -var jsonapiAdapter Renderer = &adapter.JSONAPI{} -var rawAdapter Renderer = &adapter.Raw{} - -type Renderer interface { - ContentType() string - Render(payload any, options ...any) ([]byte, error) - RenderError(err error, options ...any) ([]byte, error) -} - -type Logger interface { - Infof(format string, args ...any) - Errorf(format string, args ...any) -} - -type Options struct { - Adapter string - logger Logger -} - -type Response struct { - ResponseWriter http.ResponseWriter - adapter Renderer - options Options - statusCode int -} - -func New(r http.ResponseWriter, opts Options) *Response { - adp := rawAdapter - switch opts.Adapter { - case "jsonapi": - adp = jsonapiAdapter - default: - opts.Adapter = "raw" - } - - return &Response{ - ResponseWriter: r, - adapter: adp, - options: opts, - statusCode: http.StatusOK, - } -} - -func (r Response) logger() Logger { - if r.options.logger != nil { - return r.options.logger - } - - return logger.DefaultLogger -} - -func (r *Response) Adapter() Renderer { - if r.adapter == nil { - r.adapter = rawAdapter - } - - r.Header().Set("Content-Type", r.adapter.ContentType()) - return r.adapter -} - -func (r *Response) SetAdapter(adp Renderer) *Response { - r.adapter = adp - return r -} - -func (r *Response) JSON(payload any) *Response { - r.Header().Set("Content-Type", "application/json") - json.NewEncoder(r.ResponseWriter).Encode(payload) - - return r -} - -func (r *Response) Body(payload string) *Response { - r.ResponseWriter.Write([]byte(payload)) - - return r -} - -func (r *Response) StatusCode() int { - return r.statusCode -} - -func (r *Response) Render(payload any, options ...any) error { - b, err := r.Adapter().Render(payload, options...) - if err != nil { - return err - } - - _, err = r.Write(b) - return err -} - -func (r *Response) RenderError(payload error, options ...any) error { - return r.renderErrorWith(func() {}, payload, options...) -} - -func (r *Response) SetHeader(key, value string) *Response { - r.Header().Set(key, value) - return r -} - -func (r Response) Header() http.Header { - return r.ResponseWriter.Header() -} - -func (r *Response) Write(b []byte) (int, error) { - return r.ResponseWriter.Write(b) -} - -func (r *Response) WriteHeader(statusCode int) { - r.ResponseWriter.WriteHeader(statusCode) - r.statusCode = statusCode -} - -func (r *Response) InternalServerError() { - r.WriteHeader(http.StatusInternalServerError) -} - -func (r *Response) NotFound() { - r.WriteHeader(http.StatusNotFound) -} - -func (r *Response) BadRequest() { - r.WriteHeader(http.StatusBadRequest) -} - -func (r *Response) Unauthorized() { - r.WriteHeader(http.StatusUnauthorized) -} - -func (r *Response) Forbidden() { - r.WriteHeader(http.StatusForbidden) -} - -func (r *Response) renderErrorWith(fn func(), e error, options ...any) error { - r.logger().Errorf("%+v", e) - b, err := r.Adapter().RenderError(e, options...) - if err != nil { - return err - } - - fn() - - _, err = r.Write(b) - return err -} - -func (r *Response) InternalServerErrorWith(e error, options ...any) { - err := r.renderErrorWith(r.InternalServerError, e, options...) - if err != nil { - r.logger().Errorf("got error on rendering internal_server_error") - panic(err) - } -} - -func (r *Response) NotFoundWith(e error, options ...any) error { - return r.renderErrorWith(r.NotFound, e, options...) -} - -func (r *Response) BadRequestWith(e error, options ...any) error { - return r.renderErrorWith(r.BadRequest, e, options...) -} - -func (r *Response) UnauthorizedWith(e error, options ...any) error { - return r.renderErrorWith(r.Unauthorized, e, options...) -} - -func (r *Response) ForbiddenWith(e error, options ...any) error { - return r.renderErrorWith(r.Forbidden, e, options...) -} diff --git a/pkg/payload/fixtures/.env.test b/pkg/payload/fixtures/.env.test deleted file mode 100644 index e35f350..0000000 --- a/pkg/payload/fixtures/.env.test +++ /dev/null @@ -1,3 +0,0 @@ -PORT=3000 -DATABASE_URL=postgres://postgres:password@localhost:5432/test?sslmode=disable -FUGA= diff --git a/pkg/auth/auth.go b/pkg/toolkit/auth/auth.go similarity index 100% rename from pkg/auth/auth.go rename to pkg/toolkit/auth/auth.go diff --git a/pkg/auth/error.go b/pkg/toolkit/auth/error.go similarity index 100% rename from pkg/auth/error.go rename to pkg/toolkit/auth/error.go diff --git a/pkg/auth/helper.go b/pkg/toolkit/auth/helper.go similarity index 100% rename from pkg/auth/helper.go rename to pkg/toolkit/auth/helper.go diff --git a/pkg/auth/validate.go b/pkg/toolkit/auth/validate.go similarity index 100% rename from pkg/auth/validate.go rename to pkg/toolkit/auth/validate.go diff --git a/pkg/errors/errors.go b/pkg/toolkit/errors/errors.go similarity index 100% rename from pkg/errors/errors.go rename to pkg/toolkit/errors/errors.go diff --git a/pkg/errors/errors_test.go b/pkg/toolkit/errors/errors_test.go similarity index 100% rename from pkg/errors/errors_test.go rename to pkg/toolkit/errors/errors_test.go diff --git a/pkg/http/client/client.go b/pkg/toolkit/httpclient/client.go similarity index 100% rename from pkg/http/client/client.go rename to pkg/toolkit/httpclient/client.go diff --git a/pkg/logger/logger.go b/pkg/toolkit/logger/logger.go similarity index 100% rename from pkg/logger/logger.go rename to pkg/toolkit/logger/logger.go diff --git a/pkg/toolkit/middleware/middleware.go b/pkg/toolkit/middleware/middleware.go new file mode 100644 index 0000000..0b75356 --- /dev/null +++ b/pkg/toolkit/middleware/middleware.go @@ -0,0 +1,127 @@ +package middleware + +import ( + "bytes" + "fmt" + "io" + "net/http" + "strings" + + "github.com/version-1/gooo/pkg/core/middleware" + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +func RequestLogger(logger logger.Logger) middleware.Middleware { + return middleware.Middleware{ + Name: "RequestLogger", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + logger.Infof("%s %s", r.Method, r.URL.Path) + return true + }, + } +} + +func ResponseLogger(logger logger.Logger) middleware.Middleware { + return middleware.Middleware{ + Name: "ResponseLogger", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + // FIXME: get stats code + // logger.Infof("Status: %d", w.StatusCode()) + return true + }, + } +} + +func RequestBodyLogger(logger logger.Logger) middleware.Middleware { + return middleware.Middleware{ + Name: "RequestBodyLogger", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + defer r.Body.Close() + b, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + logger.Errorf("Error reading request body: %v", err) + return false + } + + io.Copy(w, io.MultiReader(bytes.NewReader(b), r.Body)) + if len(b) > 0 { + logger.Infof("body: %s", b) + } + return true + }, + } +} + +func RequestHeaderLogger(logger logger.Logger) middleware.Middleware { + return middleware.Middleware{ + Name: "RequestHeaderLogger", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + logger.Infof("HTTP Headers: ") + for k, v := range r.Header { + logger.Infof("%s: %s", k, v) + } + return true + }, + } +} + +func CORS(origin, methods, headers []string) middleware.Middleware { + return middleware.Middleware{ + Name: "CORS", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + w.Header().Set("Access-Control-Allow-Origin", strings.Join(origin, ", ")) + w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ", ")) + w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ", ")) + return true + }, + } +} + +func WithContext(callbacks ...func(r *http.Request) *http.Request) middleware.Middleware { + return middleware.Middleware{ + Name: "WithContext", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + for _, cb := range callbacks { + r = cb(r) + } + + return true + }, + } +} + +type Handler interface { + Match(r *http.Request) bool + Handler(w http.ResponseWriter, r *http.Request) +} + +func RequestHandler(handlers []Handler) middleware.Middleware { + return middleware.Middleware{ + Name: "RequestHandler", + If: middleware.Always, + Do: func(w http.ResponseWriter, r *http.Request) bool { + match := false + for _, handler := range handlers { + if handler.Match(r) { + handler.Handler(w, r) + match = true + break + } + } + if !match { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(fmt.Sprintf("Not found endpoint: %s", r.URL.Path))) + } + + return match + }, + } +} diff --git a/pkg/toolkit/middleware/middleware_test.go b/pkg/toolkit/middleware/middleware_test.go new file mode 100644 index 0000000..c870d7c --- /dev/null +++ b/pkg/toolkit/middleware/middleware_test.go @@ -0,0 +1 @@ +package middleware diff --git a/pkg/payload/loader.go b/pkg/toolkit/payload/loader.go similarity index 100% rename from pkg/payload/loader.go rename to pkg/toolkit/payload/loader.go diff --git a/pkg/payload/loader_test.go b/pkg/toolkit/payload/loader_test.go similarity index 100% rename from pkg/payload/loader_test.go rename to pkg/toolkit/payload/loader_test.go diff --git a/pkg/payload/payload.go b/pkg/toolkit/payload/payload.go similarity index 100% rename from pkg/payload/payload.go rename to pkg/toolkit/payload/payload.go diff --git a/pkg/presenter/jsonapi/error.go b/pkg/toolkit/presenter/jsonapi/error.go similarity index 100% rename from pkg/presenter/jsonapi/error.go rename to pkg/toolkit/presenter/jsonapi/error.go diff --git a/pkg/presenter/jsonapi/helper.go b/pkg/toolkit/presenter/jsonapi/helper.go similarity index 100% rename from pkg/presenter/jsonapi/helper.go rename to pkg/toolkit/presenter/jsonapi/helper.go diff --git a/pkg/presenter/jsonapi/jsonapi.go b/pkg/toolkit/presenter/jsonapi/jsonapi.go similarity index 100% rename from pkg/presenter/jsonapi/jsonapi.go rename to pkg/toolkit/presenter/jsonapi/jsonapi.go diff --git a/pkg/presenter/jsonapi/stringify.go b/pkg/toolkit/presenter/jsonapi/stringify.go similarity index 100% rename from pkg/presenter/jsonapi/stringify.go rename to pkg/toolkit/presenter/jsonapi/stringify.go diff --git a/pkg/presenter/view/view.go b/pkg/toolkit/presenter/view/view.go similarity index 100% rename from pkg/presenter/view/view.go rename to pkg/toolkit/presenter/view/view.go diff --git a/pkg/strings/strings.go b/pkg/toolkit/strings/strings.go similarity index 100% rename from pkg/strings/strings.go rename to pkg/toolkit/strings/strings.go diff --git a/pkg/strings/strings_test.go b/pkg/toolkit/strings/strings_test.go similarity index 100% rename from pkg/strings/strings_test.go rename to pkg/toolkit/strings/strings_test.go diff --git a/pkg/testing/cleaner/adapter/pq.go b/pkg/toolkit/testing/cleaner/adapter/pq.go similarity index 100% rename from pkg/testing/cleaner/adapter/pq.go rename to pkg/toolkit/testing/cleaner/adapter/pq.go diff --git a/pkg/testing/cleaner/cleaner.go b/pkg/toolkit/testing/cleaner/cleaner.go similarity index 100% rename from pkg/testing/cleaner/cleaner.go rename to pkg/toolkit/testing/cleaner/cleaner.go diff --git a/pkg/testing/table.go b/pkg/toolkit/testing/table.go similarity index 100% rename from pkg/testing/table.go rename to pkg/toolkit/testing/table.go diff --git a/pkg/util/util.go b/pkg/toolkit/util/util.go similarity index 100% rename from pkg/util/util.go rename to pkg/toolkit/util/util.go diff --git a/pkg/util/util_test.go b/pkg/toolkit/util/util_test.go similarity index 100% rename from pkg/util/util_test.go rename to pkg/toolkit/util/util_test.go From a4312dc87d41138236765ef35449352a2ff26ba6 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 14 Dec 2024 19:35:37 -0800 Subject: [PATCH 02/20] Add Walk func for route group --- examples/bare/cmd/app.go | 6 +++++- pkg/core/route/group.go | 6 ++++++ pkg/core/route/handler.go | 2 +- pkg/toolkit/middleware/middleware.go | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index 1c359b7..b58661b 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -10,6 +10,7 @@ import ( "github.com/version-1/gooo/pkg/core/response" "github.com/version-1/gooo/pkg/core/route" "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/version-1/gooo/pkg/toolkit/middleware" ) func main() { @@ -28,7 +29,7 @@ func main() { users := route.GroupHandler{ Path: "/users", Handlers: []route.HandlerInterface{ - route.JSON[request.Void, any]().Get("", func(res *response.Response[any], req *request.Request[request.Void]) { + route.JSON[request.Void, map[string]string]().Get("", func(res *response.Response[map[string]string], req *request.Request[request.Void]) { res.Render(map[string]string{"message": "ok"}) }), route.JSON[any, any]().Post("", func(res *response.Response[any], req *request.Request[any]) { @@ -48,6 +49,9 @@ func main() { handlers := users.List() app.WithDefaultMiddlewares(server, handlers) + route.Walk(handlers, func(h middleware.Handler) { + server.Logger().Infof("%s", h.String()) + }) ctx := context.Background() if err := server.Run(ctx); err != nil { diff --git a/pkg/core/route/group.go b/pkg/core/route/group.go index d5eb2c0..0de9c14 100644 --- a/pkg/core/route/group.go +++ b/pkg/core/route/group.go @@ -27,3 +27,9 @@ func (g GroupHandler) List() []middleware.Handler { return list } + +func Walk(list []middleware.Handler, fn func(h middleware.Handler)) { + for _, h := range list { + fn(h) + } +} diff --git a/pkg/core/route/handler.go b/pkg/core/route/handler.go index b2a9795..0992739 100644 --- a/pkg/core/route/handler.go +++ b/pkg/core/route/handler.go @@ -29,7 +29,7 @@ func (h *Handler[I, O]) ShiftPath(base string) { } func (h Handler[I, O]) String() string { - return fmt.Sprintf("Handler [%s] %s", h.Method, h.Path) + return fmt.Sprintf("[%s] %s", h.Method, h.Path) } func (h Handler[I, O]) Handler(res http.ResponseWriter, req *http.Request) { diff --git a/pkg/toolkit/middleware/middleware.go b/pkg/toolkit/middleware/middleware.go index 0b75356..dfd9261 100644 --- a/pkg/toolkit/middleware/middleware.go +++ b/pkg/toolkit/middleware/middleware.go @@ -99,6 +99,7 @@ func WithContext(callbacks ...func(r *http.Request) *http.Request) middleware.Mi } type Handler interface { + fmt.Stringer Match(r *http.Request) bool Handler(w http.ResponseWriter, r *http.Request) } From 4f80ab92418103caee9926ab35d6c76e84078117 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 14 Dec 2024 20:10:27 -0800 Subject: [PATCH 03/20] Fix for nested routes --- examples/bare/cmd/app.go | 11 +++++++---- pkg/core/app/helper.go | 9 +++++++-- pkg/core/route/group.go | 16 +++++++++------- pkg/core/route/handler.go | 16 ++++++++++++++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index b58661b..16e905e 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -26,6 +26,7 @@ func main() { w.Write([]byte("Internal server error")) }, } + users := route.GroupHandler{ Path: "/users", Handlers: []route.HandlerInterface{ @@ -46,10 +47,12 @@ func main() { }), }, } - - handlers := users.List() - app.WithDefaultMiddlewares(server, handlers) - route.Walk(handlers, func(h middleware.Handler) { + apiv1 := route.GroupHandler{ + Path: "/api/v1", + } + apiv1.Add(users.Children()...) + app.WithDefaultMiddlewares(server, apiv1.Children()...) + route.Walk(apiv1.Children(), func(h middleware.Handler) { server.Logger().Infof("%s", h.String()) }) diff --git a/pkg/core/app/helper.go b/pkg/core/app/helper.go index 18d6df6..4562e0c 100644 --- a/pkg/core/app/helper.go +++ b/pkg/core/app/helper.go @@ -5,11 +5,16 @@ import ( "github.com/version-1/gooo/pkg/core/context" "github.com/version-1/gooo/pkg/core/middleware" + "github.com/version-1/gooo/pkg/core/route" helper "github.com/version-1/gooo/pkg/toolkit/middleware" ) -func WithDefaultMiddlewares(a *App, handlers []helper.Handler) middleware.Middlewares { +func WithDefaultMiddlewares(a *App, handlers ...route.HandlerInterface) middleware.Middlewares { + _handlers := make([]helper.Handler, len(handlers)) + for i, h := range handlers { + _handlers[i] = h + } a.Middlewares = middleware.Middlewares([]middleware.Middleware{ helper.WithContext( func(r *http.Request) *http.Request { @@ -21,7 +26,7 @@ func WithDefaultMiddlewares(a *App, handlers []helper.Handler) middleware.Middle ), helper.RequestLogger(a.Logger()), helper.RequestBodyLogger(a.Logger()), - helper.RequestHandler(handlers), + helper.RequestHandler(_handlers), helper.ResponseLogger(a.Logger()), }) diff --git a/pkg/core/route/group.go b/pkg/core/route/group.go index 0de9c14..44c3522 100644 --- a/pkg/core/route/group.go +++ b/pkg/core/route/group.go @@ -5,30 +5,32 @@ import ( ) type GroupHandler struct { - Path string + Path string + // We use HandlerInterface instead of route.Handler because route.Handler is generic, + // which prevents us from determining the concrete type of the handler list. Handlers []HandlerInterface } type HandlerInterface interface { middleware.Handler - ShiftPath(string) + ShiftPath(string) HandlerInterface } func (g *GroupHandler) Add(h ...HandlerInterface) { g.Handlers = append(g.Handlers, h...) } -func (g GroupHandler) List() []middleware.Handler { - list := make([]middleware.Handler, len(g.Handlers)) +func (g GroupHandler) Children() []HandlerInterface { + list := make([]HandlerInterface, len(g.Handlers)) for i, h := range g.Handlers { - h.ShiftPath(g.Path) - list[i] = h + shifted := h.ShiftPath(g.Path) + list[i] = shifted } return list } -func Walk(list []middleware.Handler, fn func(h middleware.Handler)) { +func Walk(list []HandlerInterface, fn func(h middleware.Handler)) { for _, h := range list { fn(h) } diff --git a/pkg/core/route/handler.go b/pkg/core/route/handler.go index 0992739..b1be1db 100644 --- a/pkg/core/route/handler.go +++ b/pkg/core/route/handler.go @@ -24,8 +24,20 @@ type Handler[I, O any] struct { adapter response.Adapter } -func (h *Handler[I, O]) ShiftPath(base string) { - h.Path = filepath.Clean(base + "/" + h.Path) +func (h *Handler[I, O]) clone() *Handler[I, O] { + return &Handler[I, O]{ + Path: h.Path, + Method: h.Method, + handler: h.handler, + params: h.params, + adapter: h.adapter, + } +} + +func (h *Handler[I, O]) ShiftPath(base string) HandlerInterface { + cloned := h.clone() + cloned.Path = filepath.Clean(base + "/" + h.Path) + return cloned } func (h Handler[I, O]) String() string { From 7645e1f425684e66efa9662cd1f5d0da10f46dcf Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 14 Dec 2024 21:38:23 -0800 Subject: [PATCH 04/20] Fix for post endpoint --- examples/bare/cmd/app.go | 34 +++++++++++++++++++++++++--- pkg/core/app/helper.go | 2 +- pkg/core/context/context.go | 10 +++++++- pkg/core/request/request.go | 7 +++++- pkg/core/response/response.go | 25 ++++++++++++-------- pkg/toolkit/middleware/middleware.go | 13 +++++++---- 6 files changed, 71 insertions(+), 20 deletions(-) diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index 16e905e..0bf7c88 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -4,6 +4,7 @@ import ( "context" "log" "net/http" + "time" "github.com/version-1/gooo/pkg/core/app" "github.com/version-1/gooo/pkg/core/request" @@ -13,6 +14,19 @@ import ( "github.com/version-1/gooo/pkg/toolkit/middleware" ) +type User struct { + ID string `json:"id"` + Username string `json:"name"` + Email string `json:"email"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +type UserCreate struct { + Username string `json:"name"` + Email string `json:"email"` +} + func main() { cfg := &app.Config{} cfg.SetLogger(logger.DefaultLogger) @@ -21,7 +35,7 @@ func main() { Addr: ":8080", Config: cfg, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { - cfg.Logger().Errorf("Error: %v", err) + cfg.Logger().Errorf("Error: %+v", err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Internal server error")) }, @@ -33,8 +47,22 @@ func main() { route.JSON[request.Void, map[string]string]().Get("", func(res *response.Response[map[string]string], req *request.Request[request.Void]) { res.Render(map[string]string{"message": "ok"}) }), - route.JSON[any, any]().Post("", func(res *response.Response[any], req *request.Request[any]) { - res.Render(map[string]string{"message": "ok"}) + route.JSON[UserCreate, User]().Post("", func(res *response.Response[User], req *request.Request[UserCreate]) { + body, err := req.Body() + if err != nil { + res.BadRequest(err) + return + } + + now := time.Now() + user := User{ + ID: "1", + Username: body.Username, + Email: body.Email, + Created: now, + Updated: now, + } + res.Render(user) }), route.JSON[request.Void, any]().Get(":id", func(res *response.Response[any], req *request.Request[request.Void]) { res.Render(map[string]string{"message": "ok"}) diff --git a/pkg/core/app/helper.go b/pkg/core/app/helper.go index 4562e0c..489a353 100644 --- a/pkg/core/app/helper.go +++ b/pkg/core/app/helper.go @@ -27,7 +27,7 @@ func WithDefaultMiddlewares(a *App, handlers ...route.HandlerInterface) middlewa helper.RequestLogger(a.Logger()), helper.RequestBodyLogger(a.Logger()), helper.RequestHandler(_handlers), - helper.ResponseLogger(a.Logger()), + helper.ResponseLogger(a.Logger()), // TODO: not implemented }) return a.Middlewares diff --git a/pkg/core/context/context.go b/pkg/core/context/context.go index 9fec123..818c166 100644 --- a/pkg/core/context/context.go +++ b/pkg/core/context/context.go @@ -2,6 +2,8 @@ package context import ( "context" + "errors" + "fmt" ) const ( @@ -9,7 +11,13 @@ const ( ) func Get[T any](ctx context.Context, key string) T { - return ctx.Value(key).(T) + v, ok := ctx.Value(key).(T) + if !ok { + err := errors.New(fmt.Sprintf("context value not found: %s", key)) + panic(err) + } + + return v } func With[T any](ctx context.Context, key string, value T) context.Context { diff --git a/pkg/core/request/request.go b/pkg/core/request/request.go index 559d52f..fd7b018 100644 --- a/pkg/core/request/request.go +++ b/pkg/core/request/request.go @@ -2,6 +2,7 @@ package request import ( gocontext "context" + "encoding/json" "io" "net/http" @@ -42,8 +43,12 @@ func (r *Request[I]) Body() (I, error) { r.body = &b } - return res, nil + if err := json.Unmarshal(*r.body, &res); err != nil { + r.Logger().Errorf("failed to unmarshal request body: %s", err) + return res, nil + } + return res, nil } type loggerGetter interface { diff --git a/pkg/core/response/response.go b/pkg/core/response/response.go index eb91fcb..70717a3 100644 --- a/pkg/core/response/response.go +++ b/pkg/core/response/response.go @@ -37,26 +37,31 @@ func (r *Response[O]) WriteHeader(code int) { r.status = code } -func (r Response[O]) renderError(w http.ResponseWriter, err error) { - r.adapter.Error(w, err, r.status) +func (r Response[O]) renderError(err error) { + r.adapter.Error(r.ResponseWriter, err, r.status) } -func (r Response[O]) InternalServerError(w http.ResponseWriter, err error) { +func (r Response[O]) InternalServerError(err error) { r.status = http.StatusInternalServerError - r.renderError(w, err) + r.renderError(err) } -func (r Response[O]) NotFound(w http.ResponseWriter, err error) { +func (r Response[O]) NotFound(err error) { r.status = http.StatusNotFound - r.renderError(w, err) + r.renderError(err) } -func (r Response[O]) BadRequest(w http.ResponseWriter, err error) { +func (r Response[O]) BadRequest(err error) { r.status = http.StatusBadRequest - r.renderError(w, err) + r.renderError(err) } -func (r Response[O]) UnprocessableEntity(w http.ResponseWriter, err error) { +func (r Response[O]) UnprocessableEntity(err error) { r.status = http.StatusUnprocessableEntity - r.renderError(w, err) + r.renderError(err) +} + +func (r Response[O]) Unauthorized(err error) { + r.status = http.StatusUnauthorized + r.renderError(err) } diff --git a/pkg/toolkit/middleware/middleware.go b/pkg/toolkit/middleware/middleware.go index dfd9261..d8a6788 100644 --- a/pkg/toolkit/middleware/middleware.go +++ b/pkg/toolkit/middleware/middleware.go @@ -37,9 +37,10 @@ func ResponseLogger(logger logger.Logger) middleware.Middleware { func RequestBodyLogger(logger logger.Logger) middleware.Middleware { return middleware.Middleware{ Name: "RequestBodyLogger", - If: middleware.Always, + If: func(r *http.Request) bool { + return r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch + }, Do: func(w http.ResponseWriter, r *http.Request) bool { - defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -48,10 +49,13 @@ func RequestBodyLogger(logger logger.Logger) middleware.Middleware { return false } - io.Copy(w, io.MultiReader(bytes.NewReader(b), r.Body)) if len(b) > 0 { logger.Infof("body: %s", b) } + + r.Body.Close() + r.Body = io.NopCloser(bytes.NewReader(b)) + return true }, } @@ -90,7 +94,8 @@ func WithContext(callbacks ...func(r *http.Request) *http.Request) middleware.Mi If: middleware.Always, Do: func(w http.ResponseWriter, r *http.Request) bool { for _, cb := range callbacks { - r = cb(r) + req := cb(r) + *r = *req } return true From ca60513c48c917be6ae6ee5a371c7a336dd37634 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 21 Dec 2024 19:05:45 -0800 Subject: [PATCH 05/20] Add swagger endpoint --- examples/bare/cmd/app.go | 20 +++++++++ examples/bare/internal/swagger/swagger.go | 48 ++++++++++++++++++++++ examples/bare/internal/swagger/swagger.yml | 26 ++++++++++++ pkg/core/response/adapter.go | 28 +++++++++++++ pkg/core/route/factory.go | 10 ++++- 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 examples/bare/internal/swagger/swagger.go create mode 100644 examples/bare/internal/swagger/swagger.yml diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index 0bf7c88..2efd525 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/version-1/gooo/examples/bare/internal/swagger" "github.com/version-1/gooo/pkg/core/app" "github.com/version-1/gooo/pkg/core/request" "github.com/version-1/gooo/pkg/core/response" @@ -75,10 +76,29 @@ func main() { }), }, } + swagger := route.GroupHandler{ + Path: "/swagger", + Handlers: []route.HandlerInterface{ + route.HTML[request.Void]().Get("", func(res *response.Response[[]byte], req *request.Request[request.Void]) { + res.Render(swagger.Index()) + }), + route.Text[request.Void]().Get("swagger.yml", func(res *response.Response[[]byte], req *request.Request[request.Void]) { + b, err := swagger.SwaggerYAML() + if err != nil { + res.InternalServerError(err) + return + } + + res.Render(b) + }), + }, + } + apiv1 := route.GroupHandler{ Path: "/api/v1", } apiv1.Add(users.Children()...) + apiv1.Add(swagger.Children()...) app.WithDefaultMiddlewares(server, apiv1.Children()...) route.Walk(apiv1.Children(), func(h middleware.Handler) { server.Logger().Infof("%s", h.String()) diff --git a/examples/bare/internal/swagger/swagger.go b/examples/bare/internal/swagger/swagger.go new file mode 100644 index 0000000..c011634 --- /dev/null +++ b/examples/bare/internal/swagger/swagger.go @@ -0,0 +1,48 @@ +package swagger + +import ( + "embed" + "fmt" + "io/fs" +) + +const hostURL = "http://localhost:8080" + +func Index() []byte { + return []byte(fmt.Sprintf(` + + + + Swagger + + + + + +
+ + + + + `, hostURL)) + +} + +//go:embed *.yml +var swaggerConf embed.FS + +func SwaggerYAML() ([]byte, error) { + f, err := fs.ReadFile(swaggerConf, "swagger.yml") + if err != nil { + return f, err + } + + return f, nil +} diff --git a/examples/bare/internal/swagger/swagger.yml b/examples/bare/internal/swagger/swagger.yml new file mode 100644 index 0000000..fc99002 --- /dev/null +++ b/examples/bare/internal/swagger/swagger.yml @@ -0,0 +1,26 @@ +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 + +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing + +paths: + /users: + get: + summary: Returns a list of users. + description: Optional extended description in CommonMark or HTML. + responses: + "200": # status code + description: A JSON array of user names + content: + application/json: + schema: + type: array + items: + type: string diff --git a/pkg/core/response/adapter.go b/pkg/core/response/adapter.go index 555715f..739b872 100644 --- a/pkg/core/response/adapter.go +++ b/pkg/core/response/adapter.go @@ -55,3 +55,31 @@ func (a HTMLAdapter) Error(w http.ResponseWriter, err error, status int) { panic(err) } } + +type TextAdapter struct{} + +func (a TextAdapter) Render(w http.ResponseWriter, payload any, status int) error { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(status) + + body, ok := payload.([]byte) + if !ok { + return fmt.Errorf("body must be []byte but got %T", payload) + } + _, err := w.Write(body) + return err +} + +func (a TextAdapter) Error(w http.ResponseWriter, err error, status int) { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(status) + + body := []byte(fmt.Sprintf(` + Error: %s \n + Status: %d + `, err, status)) + + if _, err := w.Write(body); err != nil { + panic(err) + } +} diff --git a/pkg/core/route/factory.go b/pkg/core/route/factory.go index 805dace..1ef9436 100644 --- a/pkg/core/route/factory.go +++ b/pkg/core/route/factory.go @@ -12,12 +12,18 @@ func JSON[I, O any]() *Handler[I, O] { } } -func HTML[I, O any]() *Handler[I, O] { - return &Handler[I, O]{ +func HTML[I any]() *Handler[I, []byte] { + return &Handler[I, []byte]{ adapter: response.HTMLAdapter{}, } } +func Text[I any]() *Handler[I, []byte] { + return &Handler[I, []byte]{ + adapter: response.TextAdapter{}, + } +} + func (h *Handler[I, O]) Get(path string, handler HandlerFunc[I, O]) *Handler[I, O] { h.Path = path h.Method = http.MethodGet From 7ff8c564991d477c6fb433cdc239d04f13c0c464 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 4 Jan 2025 12:57:26 +0900 Subject: [PATCH 06/20] Add schemav2 package --- examples/bare/internal/swagger/swagger.yml | 26 +++++- examples/core/cmd/app.go | 12 +++ pkg/core/schemav2/schema.go | 94 ++++++++++++++++++++++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 pkg/core/schemav2/schema.go diff --git a/examples/bare/internal/swagger/swagger.yml b/examples/bare/internal/swagger/swagger.yml index fc99002..6697714 100644 --- a/examples/bare/internal/swagger/swagger.yml +++ b/examples/bare/internal/swagger/swagger.yml @@ -5,10 +5,8 @@ info: version: 0.1.9 servers: - - url: http://api.example.com/v1 + - url: http://localhost:8080/api/v1 description: Optional server description, e.g. Main (production) server - - url: http://staging-api.example.com - description: Optional server description, e.g. Internal staging server for testing paths: /users: @@ -23,4 +21,24 @@ paths: schema: type: array items: - type: string + $ref: '#/components/schemas/User' +components: + schemas: + Error: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + sample: "John Doe" + diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index da29a2c..66beca7 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -1,4 +1,16 @@ package main +import ( + "fmt" + + schema "github.com/version-1/gooo/pkg/core/schemav2" +) + func main() { + s, err := schema.New("./examples/bare/internal/swagger/swagger.yml") + if err != nil { + panic(err) + } + + fmt.Printf("%#v\n", s) } diff --git a/pkg/core/schemav2/schema.go b/pkg/core/schemav2/schema.go new file mode 100644 index 0000000..de7c94f --- /dev/null +++ b/pkg/core/schemav2/schema.go @@ -0,0 +1,94 @@ +package schema + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +func New(path string) (*RootSchema, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, err + } + s := &RootSchema{} + if err := yaml.Unmarshal(bytes, &s); err != nil { + return s, err + } + + return s, nil +} + +type RequestBody struct { + Description string `json:"description"` + Content map[string]interface{} `json:"content"` +} +type Responses map[string]Response + +type Response struct { + Description string `json:"description"` + Content map[string]interface{} `json:"content"` +} + +type Content struct { + Schema RootSchema `json:"schema"` +} + +type Parameter struct { + Name string `json:"name"` + In string `json:"in"` + Description string `json:"description"` + Required bool `json:"required"` + Schema RootSchema `json:"schema"` +} + +type Operation struct { + Summary string `json:"summary"` + Description string `json:"description"` + OperationId string `json:"operationId"` + Parameters []Parameter `json:"parameters"` + RequestBody RequestBody `json:"requestBody"` + Responses Responses `json:"responses"` +} + +type PathItem struct { + Get Operation `json:"get"` + Post Operation `json:"post"` + Put Operation `json:"put"` + Delete Operation `json:"delete"` +} + +type Info struct { + Title string `json:"title"` + Description string `json:"description"` + Version string `json:"version"` +} + +type Server struct { + Url string `json:"url"` + Description string `json:"description"` +} + +type Components struct { + Schemas map[string]Schema `json:"schemas"` +} + +type Schema struct { + Type string `json:"type"` + Properties map[string]Property `json:"properties"` + Ref string `json:"$ref"` +} + +type Property struct { + Type string `json:"type"` + Format string `json:"format"` + Sample string `json:"sample"` +} + +type RootSchema struct { + OpenAPI string `json:"openapi"` + Info Info `json:"info"` + Paths map[string]PathItem `json:"paths"` + Servers []Server `json:"servers"` + Components Components +} From 8d8d6f3ea0ef66e45b23c6831ff61c6e564bd3f8 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sat, 4 Jan 2025 13:44:40 +0900 Subject: [PATCH 07/20] Impl gen main.go code --- examples/bare/internal/swagger/swagger.yml | 43 ++++++++++++++++++++ examples/core/cmd/app.go | 8 ++-- examples/core/generated/main.go | 42 ++++++++++++++++++++ pkg/core/generator/generator.go | 4 +- pkg/core/schemav2/components/entry.go.tmpl | 32 +++++++++++++++ pkg/core/schemav2/generate.go | 46 ++++++++++++++++++++++ pkg/core/schemav2/schema.go | 4 +- pkg/core/schemav2/template.go | 29 ++++++++++++++ pkg/toolkit/util/util.go | 2 +- 9 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 examples/core/generated/main.go create mode 100644 pkg/core/schemav2/components/entry.go.tmpl create mode 100644 pkg/core/schemav2/generate.go create mode 100644 pkg/core/schemav2/template.go diff --git a/examples/bare/internal/swagger/swagger.yml b/examples/bare/internal/swagger/swagger.yml index 6697714..0ab21c5 100644 --- a/examples/bare/internal/swagger/swagger.yml +++ b/examples/bare/internal/swagger/swagger.yml @@ -22,6 +22,49 @@ paths: type: array items: $ref: '#/components/schemas/User' + post: + summary: Create a User. + description: Optional extended description in CommonMark or HTML. + responses: + "201": # status code + description: A JSON array of user names + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + /users/{id}: + get: + summary: Returns a user. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of user names + content: + application/json: + schema: + $ref: '#/components/schemas/User' + patch: + summary: Returns a user. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of user names + content: + application/json: + schema: + $ref: '#/components/schemas/User' + delete: + summary: Delete a user. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of user names + content: + application/json: + schema: + $ref: '#/components/schemas/User' components: schemas: Error: diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index 66beca7..3377fde 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - schema "github.com/version-1/gooo/pkg/core/schemav2" ) @@ -12,5 +10,9 @@ func main() { panic(err) } - fmt.Printf("%#v\n", s) + g := schema.NewGenerator(s, "./examples/core/generated") + + if err := g.Generate(); err != nil { + panic(err) + } } diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go new file mode 100644 index 0000000..95fecc4 --- /dev/null +++ b/examples/core/generated/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "log" + "net/http" + + "github.com/version-1/gooo/pkg/core/app" + "github.com/version-1/gooo/pkg/core/route" + "github.com/version-1/gooo/pkg/toolkit/logger" +) + +func main() { + cfg := &app.Config{} + cfg.SetLogger(logger.DefaultLogger) + + server := &app.App{ + Addr: ":8080", + Config: cfg, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + cfg.Logger().Errorf("Error: %+v", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + }, + } + + RegisterRoutes(server) + ctx := context.Background() + if err := server.Run(ctx); err != nil { + log.Fatalf("failed to run app: %s", err) + } +} + +func RegisterRoutes(srv *app.App) { + routes := route.GroupHandler{ + Path: "/users", + Handlers: []route.HandlerInterface{ + // ここにルーティングが入ります + }, + } + app.WithDefaultMiddlewares(srv, routes.Children()...) +} diff --git a/pkg/core/generator/generator.go b/pkg/core/generator/generator.go index fe923ab..15b6376 100644 --- a/pkg/core/generator/generator.go +++ b/pkg/core/generator/generator.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/util" + "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/version-1/gooo/pkg/toolkit/util" ) type Generator struct { diff --git a/pkg/core/schemav2/components/entry.go.tmpl b/pkg/core/schemav2/components/entry.go.tmpl new file mode 100644 index 0000000..b80dd9c --- /dev/null +++ b/pkg/core/schemav2/components/entry.go.tmpl @@ -0,0 +1,32 @@ +package main + +func main() { + cfg := &app.Config{} + cfg.SetLogger(logger.DefaultLogger) + + server := &app.App{ + Addr: ":8080", + Config: cfg, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + cfg.Logger().Errorf("Error: %+v", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + }, + } + + RegisterRoutes(server) + ctx := context.Background() + if err := server.Run(ctx); err != nil { + log.Fatalf("failed to run app: %s", err) + } +} + +func RegisterRoutes(srv *app.App) { + routes := route.GroupHandler{ + Path: "/users", + Handlers: []route.HandlerInterface{ + {{ .Routes }} + }, + } + app.WithDefaultMiddlewares(srv, routes.Children()...) +} diff --git a/pkg/core/schemav2/generate.go b/pkg/core/schemav2/generate.go new file mode 100644 index 0000000..76ca2ff --- /dev/null +++ b/pkg/core/schemav2/generate.go @@ -0,0 +1,46 @@ +package schemav2 + +import ( + "go/format" + + "github.com/version-1/gooo/pkg/core/generator" + "golang.org/x/tools/imports" +) + +type Generator struct { + r *RootSchema + outputs []generator.Template + OutDir string +} + +func NewGenerator(r *RootSchema, outDir string) *Generator { + return &Generator{r: r, OutDir: outDir} +} + +func (g *Generator) Generate() error { + g.outputs = append(g.outputs, Main{ + Routes: "// ここにルーティングが入ります", + }) + for _, tmpl := range g.outputs { + g := generator.Generator{Dir: g.OutDir, Template: tmpl} + if err := g.Run(); err != nil { + return err + } + } + + return nil +} + +func pretify(filename, s string) ([]byte, error) { + formatted, err := format.Source([]byte(s)) + if err != nil { + return []byte{}, err + } + + processed, err := imports.Process(filename, formatted, nil) + if err != nil { + return formatted, err + } + + return processed, nil +} diff --git a/pkg/core/schemav2/schema.go b/pkg/core/schemav2/schema.go index de7c94f..3702845 100644 --- a/pkg/core/schemav2/schema.go +++ b/pkg/core/schemav2/schema.go @@ -1,4 +1,4 @@ -package schema +package schemav2 import ( "os" @@ -90,5 +90,5 @@ type RootSchema struct { Info Info `json:"info"` Paths map[string]PathItem `json:"paths"` Servers []Server `json:"servers"` - Components Components + Components Components `json:"components"` } diff --git a/pkg/core/schemav2/template.go b/pkg/core/schemav2/template.go new file mode 100644 index 0000000..a2ff9d5 --- /dev/null +++ b/pkg/core/schemav2/template.go @@ -0,0 +1,29 @@ +package schemav2 + +import ( + "bytes" + "embed" + "text/template" +) + +//go:embed components/*.go.tmpl +var tmpl embed.FS + +type Main struct { + Routes string +} + +func (m Main) Filename() string { + return "main" +} + +func (m Main) Render() (string, error) { + tmpl := template.Must(template.New("entry").ParseFS(tmpl, "components/entry.go.tmpl")) + var b bytes.Buffer + if err := tmpl.ExecuteTemplate(&b, "entry.go.tmpl", m); err != nil { + return "", err + } + + res, err := pretify(m.Filename(), b.String()) + return string(res), err +} diff --git a/pkg/toolkit/util/util.go b/pkg/toolkit/util/util.go index c26b789..cce6c43 100644 --- a/pkg/toolkit/util/util.go +++ b/pkg/toolkit/util/util.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/google/uuid" - "github.com/version-1/gooo/pkg/errors" + "github.com/version-1/gooo/pkg/toolkit/errors" ) func LookupGomodDirPath() (string, error) { From 3066f6d07d7ec0615d0a709161b134d41e607164 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 07:33:21 -0800 Subject: [PATCH 08/20] Impl schema generation --- examples/bare/internal/swagger/swagger.yml | 142 ++++++++++++++ examples/core/cmd/app.go | 8 +- .../core/generated/internal/schema/schema.go | 33 ++++ examples/core/generated/main.go | 37 +++- pkg/core/generator/generator.go | 16 +- pkg/core/schemav2/generate.go | 38 ++-- pkg/core/schemav2/{ => openapi}/schema.go | 40 ++-- .../{ => template}/components/entry.go.tmpl | 7 + .../schemav2/template/components/file.go.tmpl | 10 + .../template/components/route.go.tmpl | 3 + .../template/components/struct.go.tmpl | 4 + pkg/core/schemav2/template/file.go | 7 + pkg/core/schemav2/template/format.go | 29 +++ .../{template.go => template/main.go} | 19 +- pkg/core/schemav2/template/namespace.go | 15 ++ pkg/core/schemav2/template/partial/partial.go | 12 ++ pkg/core/schemav2/template/route.go | 102 ++++++++++ pkg/core/schemav2/template/schema.go | 176 ++++++++++++++++++ 18 files changed, 652 insertions(+), 46 deletions(-) create mode 100644 examples/core/generated/internal/schema/schema.go rename pkg/core/schemav2/{ => openapi}/schema.go (62%) rename pkg/core/schemav2/{ => template}/components/entry.go.tmpl (86%) create mode 100644 pkg/core/schemav2/template/components/file.go.tmpl create mode 100644 pkg/core/schemav2/template/components/route.go.tmpl create mode 100644 pkg/core/schemav2/template/components/struct.go.tmpl create mode 100644 pkg/core/schemav2/template/file.go create mode 100644 pkg/core/schemav2/template/format.go rename pkg/core/schemav2/{template.go => template/main.go} (57%) create mode 100644 pkg/core/schemav2/template/namespace.go create mode 100644 pkg/core/schemav2/template/partial/partial.go create mode 100644 pkg/core/schemav2/template/route.go create mode 100644 pkg/core/schemav2/template/schema.go diff --git a/examples/bare/internal/swagger/swagger.yml b/examples/bare/internal/swagger/swagger.yml index 0ab21c5..baf0846 100644 --- a/examples/bare/internal/swagger/swagger.yml +++ b/examples/bare/internal/swagger/swagger.yml @@ -8,9 +8,17 @@ servers: - url: http://localhost:8080/api/v1 description: Optional server description, e.g. Main (production) server +tags: + - name: User + description: Operations related to users + - name: Post + description: Operations related to posts + paths: /users: get: + tags: + - User summary: Returns a list of users. description: Optional extended description in CommonMark or HTML. responses: @@ -20,11 +28,19 @@ paths: application/json: schema: type: array + fuga: "hoge" items: $ref: '#/components/schemas/User' post: + tags: + - User summary: Create a User. description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MutateUser' responses: "201": # status code description: A JSON array of user names @@ -36,6 +52,8 @@ paths: $ref: '#/components/schemas/User' /users/{id}: get: + tags: + - User summary: Returns a user. description: Optional extended description in CommonMark or HTML. responses: @@ -46,8 +64,15 @@ paths: schema: $ref: '#/components/schemas/User' patch: + tags: + - User summary: Returns a user. description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MutateUser' responses: "200": description: A JSON array of user names @@ -56,6 +81,8 @@ paths: schema: $ref: '#/components/schemas/User' delete: + tags: + - User summary: Delete a user. description: Optional extended description in CommonMark or HTML. responses: @@ -65,6 +92,82 @@ paths: application/json: schema: $ref: '#/components/schemas/User' + /posts: + get: + tags: + - Post + summary: Returns a list of posts. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of posts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Post' + post: + tags: + - Post + summary: Create a Post. + description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MutatePost' + responses: + "201": + description: A JSON array of posts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Post' + /posts/{id}: + get: + tags: + - Post + summary: Returns a post. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of posts + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + patch: + tags: + - Post + summary: Update a post. + description: Optional extended description in CommonMark or HTML. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MutatePost' + responses: + "200": + description: A JSON array of posts + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + delete: + tags: + - Post + summary: Delete a post. + description: Optional extended description in CommonMark or HTML. + responses: + "200": + description: A JSON array of posts + content: + application/json: + schema: + $ref: '#/components/schemas/Post' components: schemas: Error: @@ -84,4 +187,43 @@ components: username: type: string sample: "John Doe" + obj: + type: object + properties: + hoge: + type: string + sample: "hoge" + fuga: + type: string + sample: "fuga" + MutateUser: + type: object + properties: + username: + type: string + sample: "John Doe" + Post: + type: object + properties: + id: + type: integer + format: int64 + userId: + type: integer + format: int64 + title: + type: string + sample: "Sample Post Title" + content: + type: string + sample: "This is a sample post content." + MutatePost: + type: object + properties: + title: + type: string + sample: "Sample Post Title" + content: + type: string + sample: "This is a sample post content." diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index 3377fde..a00aec6 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -1,18 +1,22 @@ package main import ( + "fmt" + schema "github.com/version-1/gooo/pkg/core/schemav2" + "github.com/version-1/gooo/pkg/core/schemav2/openapi" ) func main() { - s, err := schema.New("./examples/bare/internal/swagger/swagger.yml") + s, err := openapi.New("./examples/bare/internal/swagger/swagger.yml") if err != nil { panic(err) } - g := schema.NewGenerator(s, "./examples/core/generated") + g := schema.NewGenerator(s, "./examples/core/generated", "github.com/version-1/gooo/examples/core") if err := g.Generate(); err != nil { + fmt.Printf("Error: %+v\n", err) panic(err) } } diff --git a/examples/core/generated/internal/schema/schema.go b/examples/core/generated/internal/schema/schema.go new file mode 100644 index 0000000..6fa8656 --- /dev/null +++ b/examples/core/generated/internal/schema/schema.go @@ -0,0 +1,33 @@ +package schema + +// This is a generated file. DO NOT EDIT manually. + +type Error struct { + Code int + Message string +} + +type User struct { + ID int + Username string + Obj struct { + Hoge string + Fuga string + } +} + +type MutateUser struct { + Username string +} + +type Post struct { + ID int + UserId int + Title string + Content string +} + +type MutatePost struct { + Content string + Title string +} diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 95fecc4..44030c8 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -1,11 +1,15 @@ package main +// This is a generated file. DO NOT EDIT manually. import ( "context" "log" "net/http" + "github.com/version-1/gooo/examples/core/internal/schema" "github.com/version-1/gooo/pkg/core/app" + "github.com/version-1/gooo/pkg/core/request" + "github.com/version-1/gooo/pkg/core/response" "github.com/version-1/gooo/pkg/core/route" "github.com/version-1/gooo/pkg/toolkit/logger" ) @@ -33,9 +37,38 @@ func main() { func RegisterRoutes(srv *app.App) { routes := route.GroupHandler{ - Path: "/users", + Path: "/users", Handlers: []route.HandlerInterface{ - // ここにルーティングが入ります + route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something + }), + route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutatePost, schema.Post]().Post("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutatePost, schema.Post]().Patch("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Delete("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), }, } app.WithDefaultMiddlewares(srv, routes.Children()...) diff --git a/pkg/core/generator/generator.go b/pkg/core/generator/generator.go index 15b6376..0150cf4 100644 --- a/pkg/core/generator/generator.go +++ b/pkg/core/generator/generator.go @@ -33,14 +33,22 @@ func (g Generator) Run() error { return err } + return penetrateAndCreateFile(filename, s) +} + +func penetrateAndCreateFile(filename string, content string) error { + dir := filepath.Dir(filename) + err := os.MkdirAll(dir, os.ModePerm) + if err != nil { + return errors.Wrap(err) + } + f, err := os.Create(filename) if err != nil { return errors.Wrap(err) } defer f.Close() - - f.WriteString(s) - - return nil + _, err = f.WriteString(content) + return err } diff --git a/pkg/core/schemav2/generate.go b/pkg/core/schemav2/generate.go index 76ca2ff..47e50ee 100644 --- a/pkg/core/schemav2/generate.go +++ b/pkg/core/schemav2/generate.go @@ -1,26 +1,34 @@ package schemav2 import ( - "go/format" + "fmt" + "path/filepath" "github.com/version-1/gooo/pkg/core/generator" - "golang.org/x/tools/imports" + "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/core/schemav2/template" ) type Generator struct { - r *RootSchema + r *openapi.RootSchema outputs []generator.Template + baseURL string OutDir string } -func NewGenerator(r *RootSchema, outDir string) *Generator { - return &Generator{r: r, OutDir: outDir} +func NewGenerator(r *openapi.RootSchema, outDir string, baseURL string) *Generator { + return &Generator{r: r, OutDir: outDir, baseURL: baseURL} } func (g *Generator) Generate() error { - g.outputs = append(g.outputs, Main{ - Routes: "// ここにルーティングが入ります", - }) + schemaFile := template.SchemaFile{Schema: g.r, PackageName: "schema"} + mainFile := template.Main{Schema: g.r} + + mainFile.Dependencies = []string{fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(schemaFile.Filename()))} + + g.outputs = append(g.outputs, schemaFile) + g.outputs = append(g.outputs, mainFile) + for _, tmpl := range g.outputs { g := generator.Generator{Dir: g.OutDir, Template: tmpl} if err := g.Run(); err != nil { @@ -30,17 +38,3 @@ func (g *Generator) Generate() error { return nil } - -func pretify(filename, s string) ([]byte, error) { - formatted, err := format.Source([]byte(s)) - if err != nil { - return []byte{}, err - } - - processed, err := imports.Process(filename, formatted, nil) - if err != nil { - return formatted, err - } - - return processed, nil -} diff --git a/pkg/core/schemav2/schema.go b/pkg/core/schemav2/openapi/schema.go similarity index 62% rename from pkg/core/schemav2/schema.go rename to pkg/core/schemav2/openapi/schema.go index 3702845..363161b 100644 --- a/pkg/core/schemav2/schema.go +++ b/pkg/core/schemav2/openapi/schema.go @@ -1,4 +1,4 @@ -package schemav2 +package openapi import ( "os" @@ -20,14 +20,19 @@ func New(path string) (*RootSchema, error) { } type RequestBody struct { - Description string `json:"description"` - Content map[string]interface{} `json:"content"` + Description string `json:"description"` + Content map[string]MediaType `json:"content"` } + type Responses map[string]Response type Response struct { - Description string `json:"description"` - Content map[string]interface{} `json:"content"` + Description string `json:"description"` + Content map[string]MediaType `json:"content"` +} + +type MediaType struct { + Schema Schema `json:"schema"` } type Content struct { @@ -47,15 +52,16 @@ type Operation struct { Description string `json:"description"` OperationId string `json:"operationId"` Parameters []Parameter `json:"parameters"` - RequestBody RequestBody `json:"requestBody"` + RequestBody RequestBody `json:"requestBody" yaml:"requestBody"` Responses Responses `json:"responses"` } type PathItem struct { - Get Operation `json:"get"` - Post Operation `json:"post"` - Put Operation `json:"put"` - Delete Operation `json:"delete"` + Get *Operation `json:"get"` + Post *Operation `json:"post"` + Put *Operation `json:"put"` + Patch *Operation `json:"patch"` + Delete *Operation `json:"delete"` } type Info struct { @@ -76,15 +82,21 @@ type Components struct { type Schema struct { Type string `json:"type"` Properties map[string]Property `json:"properties"` - Ref string `json:"$ref"` + Ref string `json:"$ref" yaml:"$ref"` + Items Property `json:"items"` } type Property struct { - Type string `json:"type"` - Format string `json:"format"` - Sample string `json:"sample"` + Ref string `json:"$ref" yaml:"$ref"` + Type string `json:"type"` + Properties map[string]Property `json:"properties"` + Items *Property `json:"items"` + Format string `json:"format"` + Sample string `json:"sample"` + Required bool `json:"required"` } +// version. 3.0.x type RootSchema struct { OpenAPI string `json:"openapi"` Info Info `json:"info"` diff --git a/pkg/core/schemav2/components/entry.go.tmpl b/pkg/core/schemav2/template/components/entry.go.tmpl similarity index 86% rename from pkg/core/schemav2/components/entry.go.tmpl rename to pkg/core/schemav2/template/components/entry.go.tmpl index b80dd9c..6697103 100644 --- a/pkg/core/schemav2/components/entry.go.tmpl +++ b/pkg/core/schemav2/template/components/entry.go.tmpl @@ -1,5 +1,12 @@ package main +// This is a generated file. DO NOT EDIT manually. +import ( + {{ range .Dependencies }} + "{{ . }}" + {{ end }} +) + func main() { cfg := &app.Config{} cfg.SetLogger(logger.DefaultLogger) diff --git a/pkg/core/schemav2/template/components/file.go.tmpl b/pkg/core/schemav2/template/components/file.go.tmpl new file mode 100644 index 0000000..b67093c --- /dev/null +++ b/pkg/core/schemav2/template/components/file.go.tmpl @@ -0,0 +1,10 @@ +package {{ .PackageName }} + +import ( + {{ range .Dependencies }} + "{{ . }}" + {{ end }} +) +// This is a generated file. DO NOT EDIT manually. + +{{ .Content }} diff --git a/pkg/core/schemav2/template/components/route.go.tmpl b/pkg/core/schemav2/template/components/route.go.tmpl new file mode 100644 index 0000000..5495507 --- /dev/null +++ b/pkg/core/schemav2/template/components/route.go.tmpl @@ -0,0 +1,3 @@ +route.JSON[{{.InputType}}, {{.OutputType}}]().{{.Method}}("{{.Path}}", func(res *response.Response[{{.OutputType}}], req *request.Request[{{.InputType}}]) { + // do something +}), diff --git a/pkg/core/schemav2/template/components/struct.go.tmpl b/pkg/core/schemav2/template/components/struct.go.tmpl new file mode 100644 index 0000000..f9be4c5 --- /dev/null +++ b/pkg/core/schemav2/template/components/struct.go.tmpl @@ -0,0 +1,4 @@ +type {{.TypeName}} struct { + {{range .Fields}}{{.}} + {{end}} +} diff --git a/pkg/core/schemav2/template/file.go b/pkg/core/schemav2/template/file.go new file mode 100644 index 0000000..27289f2 --- /dev/null +++ b/pkg/core/schemav2/template/file.go @@ -0,0 +1,7 @@ +package template + +type file struct { + Dependencies []string + PackageName string + Content string +} diff --git a/pkg/core/schemav2/template/format.go b/pkg/core/schemav2/template/format.go new file mode 100644 index 0000000..0cbf44f --- /dev/null +++ b/pkg/core/schemav2/template/format.go @@ -0,0 +1,29 @@ +package template + +import ( + "go/format" + + "golang.org/x/tools/imports" +) + +func pretify(filename, s string) ([]byte, error) { + formatted, err := format.Source([]byte(s)) + if err != nil { + return []byte{}, err + } + + processed, err := imports.Process(filename, formatted, nil) + if err != nil { + return formatted, err + } + + return processed, nil +} + +func Capitalize(s string) string { + if len(s) == 0 { + return s + } + + return string(s[0]-32) + s[1:] +} diff --git a/pkg/core/schemav2/template.go b/pkg/core/schemav2/template/main.go similarity index 57% rename from pkg/core/schemav2/template.go rename to pkg/core/schemav2/template/main.go index a2ff9d5..6e174fd 100644 --- a/pkg/core/schemav2/template.go +++ b/pkg/core/schemav2/template/main.go @@ -1,16 +1,21 @@ -package schemav2 +package template import ( "bytes" "embed" "text/template" + + "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/toolkit/errors" ) //go:embed components/*.go.tmpl var tmpl embed.FS type Main struct { - Routes string + Schema *openapi.RootSchema + Dependencies []string + Routes string } func (m Main) Filename() string { @@ -18,6 +23,13 @@ func (m Main) Filename() string { } func (m Main) Render() (string, error) { + routes, err := renderRoutes(extractRoutes(m.Schema)) + if err != nil { + return "", err + } + + m.Routes = routes + tmpl := template.Must(template.New("entry").ParseFS(tmpl, "components/entry.go.tmpl")) var b bytes.Buffer if err := tmpl.ExecuteTemplate(&b, "entry.go.tmpl", m); err != nil { @@ -25,5 +37,8 @@ func (m Main) Render() (string, error) { } res, err := pretify(m.Filename(), b.String()) + if err != nil { + return "", errors.Wrap(err) + } return string(res), err } diff --git a/pkg/core/schemav2/template/namespace.go b/pkg/core/schemav2/template/namespace.go new file mode 100644 index 0000000..9c7fb9f --- /dev/null +++ b/pkg/core/schemav2/template/namespace.go @@ -0,0 +1,15 @@ +package template + +import ( + "fmt" + "strings" +) + +func withSchemaPackageName(schemaName string) string { + return fmt.Sprintf("schema.%s", schemaName) +} + +func schemaTypeName(schemaName string) string { + segments := strings.Split(schemaName, "/") + return segments[len(segments)-1] +} diff --git a/pkg/core/schemav2/template/partial/partial.go b/pkg/core/schemav2/template/partial/partial.go new file mode 100644 index 0000000..f2e7013 --- /dev/null +++ b/pkg/core/schemav2/template/partial/partial.go @@ -0,0 +1,12 @@ +package partial + +import ( + "fmt" + "strings" +) + +func AnonymousStruct(fields []string) string { + return fmt.Sprintf(`struct { + %s + }`, strings.Join(fields, "\n")) +} diff --git a/pkg/core/schemav2/template/route.go b/pkg/core/schemav2/template/route.go new file mode 100644 index 0000000..f11423d --- /dev/null +++ b/pkg/core/schemav2/template/route.go @@ -0,0 +1,102 @@ +package template + +import ( + "bytes" + "strconv" + "strings" + "text/template" + + "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/toolkit/errors" +) + +type Route struct { + InputType string + OutputType string + Method string + Path string +} + +func renderRoutes(routes []Route) (string, error) { + var b bytes.Buffer + for _, r := range routes { + tmpl := template.Must(template.New("route").ParseFS(tmpl, "components/route.go.tmpl")) + if err := tmpl.ExecuteTemplate(&b, "route.go.tmpl", r); err != nil { + return "", errors.Wrap(err) + } + } + return b.String(), nil +} + +func extractRoutes(r *openapi.RootSchema) []Route { + routes := []Route{} + for path, pathItem := range r.Paths { + m := map[string]*openapi.Operation{ + "Get": pathItem.Get, + "Post": pathItem.Post, + "Patch": pathItem.Patch, + "Put": pathItem.Put, + "Delete": pathItem.Delete, + } + for k, v := range m { + if v == nil { + continue + } + + if k == "Get" || k == "Delete" { + route := Route{ + InputType: "request.Void", + OutputType: withSchemaPackageName(detectOutputType(v, 200, "application/json")), + Method: k, + Path: path, + } + + routes = append(routes, route) + } else { + statusCode := 200 + if k == "Post" { + statusCode = 201 + } + route := Route{ + InputType: withSchemaPackageName(detectInputType(v, "application/json")), + OutputType: withSchemaPackageName(detectOutputType(v, statusCode, "application/json")), + Method: k, + Path: path, + } + routes = append(routes, route) + } + } + } + + return routes +} + +func detectInputType(op *openapi.Operation, contentType string) string { + schema := op.RequestBody.Content[contentType].Schema + ref := "" + if schema.Ref != "" { + ref = schema.Ref + } + + if schema.Items.Type == "array" && schema.Items.Ref != "" { + ref = schema.Items.Ref + } + + schemaName := strings.Replace(ref, "#/components/schemas/", "", 1) + return schemaName +} + +func detectOutputType(op *openapi.Operation, statusCode int, contentType string) string { + schema := op.Responses[strconv.Itoa(statusCode)].Content[contentType].Schema + ref := "" + if schema.Ref != "" { + ref = schema.Ref + } + + if schema.Type == "array" && schema.Items.Ref != "" { + ref = schema.Items.Ref + } + + schemaName := strings.Replace(ref, "#/components/schemas/", "", 1) + return schemaName +} diff --git a/pkg/core/schemav2/template/schema.go b/pkg/core/schemav2/template/schema.go new file mode 100644 index 0000000..3b7980c --- /dev/null +++ b/pkg/core/schemav2/template/schema.go @@ -0,0 +1,176 @@ +package template + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/core/schemav2/template/partial" + "github.com/version-1/gooo/pkg/toolkit/errors" +) + +type SchemaFile struct { + Schema *openapi.RootSchema + PackageName string + Content string +} + +func (s SchemaFile) Filename() string { + return "internal/schema/schema" +} + +// FIXME: yaml.v3 doesnt guarantee the order of the fields and schemas +func (s SchemaFile) Render() (string, error) { + schemas := []Schema{} + for name, schema := range s.Schema.Components.Schemas { + fields, err := extractFields(schema.Properties, "") + if err != nil { + return "", err + } + schemas = append(schemas, Schema{ + Fields: fields, + TypeName: name, + }) + } + + content, err := renderSchemas(schemas) + if err != nil { + return "", err + } + + f := file{ + PackageName: s.PackageName, + Content: content, + } + + tmpl := template.Must(template.New("file").ParseFS(tmpl, "components/file.go.tmpl")) + var b bytes.Buffer + if err := tmpl.ExecuteTemplate(&b, "file.go.tmpl", f); err != nil { + return "", err + } + + res, err := pretify(s.Filename(), b.String()) + if err != nil { + fmt.Println("pretify content: ", b.String()) + return "", errors.Wrap(err) + } + return string(res), err +} + +type Schema struct { + Fields []string + TypeName string +} + +func renderSchemas(schemas []Schema) (string, error) { + var b bytes.Buffer + for _, s := range schemas { + tmpl := template.Must(template.New("struct").ParseFS(tmpl, "components/struct.go.tmpl")) + if err := tmpl.ExecuteTemplate(&b, "struct.go.tmpl", s); err != nil { + return "", errors.Wrap(err) + } + b.WriteString("\n") + } + return b.String(), nil +} + +func extractFields(props map[string]openapi.Property, prefix string) ([]string, error) { + var fields []string + for k, v := range props { + key := formatKeyname(k) + if v.Ref != "" { + fields = append(fields, key+" "+pointer(schemaTypeName(v.Ref))) + continue + } + + t, err := extractFieldType(v, prefix) + if err != nil { + return []string{}, err + } + fields = append(fields, key+" "+t) + } + return fields, nil +} + +func extractFieldType(prop openapi.Property, prefix string) (string, error) { + if prop.Ref != "" { + return prefix + pointer(schemaTypeName(prop.Ref)), nil + } + + switch { + case isPrimitive(prop.Type): + return prefix + convertGoType(prop.Type), nil + case isDate(prop.Type): + return prefix + "time.Time", nil + case isObject(prop.Type): + fields, err := extractFields(prop.Properties, prefix) + if err != nil { + return "", err + } + return prefix + partial.AnonymousStruct(fields), nil + case isArray(prop.Type): + if prop.Items == nil { + return "", fmt.Errorf("Array must have items properties. %s\n", prop.Type) + } + return extractFieldType(*prop.Items, prefix+"[]") + default: + return "", fmt.Errorf("Unknown type: %s\n", prop.Type) + } +} + +func pointer(typeName string) string { + return "*" + typeName +} + +func formatKeyname(key string) string { + if key == "id" { + return strings.ToUpper(key) + } + + return Capitalize(key) +} + +func convertGoType(t string) string { + m := map[string]string{ + "string": "string", + "number": "int", + "integer": "int", + "boolean": "bool", + "byte": "[]byte", + } + v, ok := m[t] + if !ok { + return t + } + return v +} + +func isPrimitive(t string) bool { + primitives := map[string]bool{ + "string": true, + "number": true, + "integer": true, + "boolean": true, + "byte": true, + } + _, ok := primitives[t] + return ok +} + +func isComplex(t string) bool { + return !isPrimitive(t) +} + +func isArray(t string) bool { + return t == "array" +} + +func isObject(t string) bool { + return t == "object" +} + +func isDate(t string) bool { + return t == "date" +} From 9bca22b5e3a5689f9c78f5e053380dd053c9d76c Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 07:38:29 -0800 Subject: [PATCH 09/20] Move up command pkg --- pkg/{core => }/command/migration/adapter/yaml/schema.go | 4 ++-- pkg/{core => }/command/migration/adapter/yaml/yaml.go | 2 +- pkg/{core => }/command/migration/constants/constants.go | 0 pkg/{core => }/command/migration/helper/connstr.go | 0 pkg/{core => }/command/migration/helper/helper.go | 2 +- pkg/{core => }/command/migration/migration.go | 6 +++--- pkg/{core => }/command/migration/reader/reader.go | 2 +- pkg/{core => }/command/migration/reader/record.go | 2 +- pkg/{core => }/command/migration/runner/runner.go | 4 ++-- pkg/{core => }/command/migration/runner/yaml.go | 2 +- pkg/{core => }/command/seeder/runner/helper.go | 0 pkg/{core => }/command/seeder/runner/template.go | 0 pkg/{core => }/command/seeder/seeder.go | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) rename pkg/{core => }/command/migration/adapter/yaml/schema.go (97%) rename pkg/{core => }/command/migration/adapter/yaml/yaml.go (96%) rename pkg/{core => }/command/migration/constants/constants.go (100%) rename pkg/{core => }/command/migration/helper/connstr.go (100%) rename pkg/{core => }/command/migration/helper/helper.go (94%) rename pkg/{core => }/command/migration/migration.go (97%) rename pkg/{core => }/command/migration/reader/reader.go (99%) rename pkg/{core => }/command/migration/reader/record.go (97%) rename pkg/{core => }/command/migration/runner/runner.go (96%) rename pkg/{core => }/command/migration/runner/yaml.go (96%) rename pkg/{core => }/command/seeder/runner/helper.go (100%) rename pkg/{core => }/command/seeder/runner/template.go (100%) rename pkg/{core => }/command/seeder/seeder.go (96%) diff --git a/pkg/core/command/migration/adapter/yaml/schema.go b/pkg/command/migration/adapter/yaml/schema.go similarity index 97% rename from pkg/core/command/migration/adapter/yaml/schema.go rename to pkg/command/migration/adapter/yaml/schema.go index 8e0b78d..3da6878 100644 --- a/pkg/core/command/migration/adapter/yaml/schema.go +++ b/pkg/command/migration/adapter/yaml/schema.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/db" - "github.com/version-1/gooo/pkg/errors" + "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/toolkit/errors" yaml "gopkg.in/yaml.v3" ) diff --git a/pkg/core/command/migration/adapter/yaml/yaml.go b/pkg/command/migration/adapter/yaml/yaml.go similarity index 96% rename from pkg/core/command/migration/adapter/yaml/yaml.go rename to pkg/command/migration/adapter/yaml/yaml.go index 0e82f65..b5702be 100644 --- a/pkg/core/command/migration/adapter/yaml/yaml.go +++ b/pkg/command/migration/adapter/yaml/yaml.go @@ -6,7 +6,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/helper" - "github.com/version-1/gooo/pkg/db" + "github.com/version-1/gooo/pkg/core/db" ) type YamlElement interface { diff --git a/pkg/core/command/migration/constants/constants.go b/pkg/command/migration/constants/constants.go similarity index 100% rename from pkg/core/command/migration/constants/constants.go rename to pkg/command/migration/constants/constants.go diff --git a/pkg/core/command/migration/helper/connstr.go b/pkg/command/migration/helper/connstr.go similarity index 100% rename from pkg/core/command/migration/helper/connstr.go rename to pkg/command/migration/helper/connstr.go diff --git a/pkg/core/command/migration/helper/helper.go b/pkg/command/migration/helper/helper.go similarity index 94% rename from pkg/core/command/migration/helper/helper.go rename to pkg/command/migration/helper/helper.go index c6e386f..b6e33c3 100644 --- a/pkg/core/command/migration/helper/helper.go +++ b/pkg/command/migration/helper/helper.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - goooerrors "github.com/version-1/gooo/pkg/errors" + goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" "github.com/version-1/gooo/pkg/command/migration/constants" ) diff --git a/pkg/core/command/migration/migration.go b/pkg/command/migration/migration.go similarity index 97% rename from pkg/core/command/migration/migration.go rename to pkg/command/migration/migration.go index 68e4b66..440e6a6 100644 --- a/pkg/core/command/migration/migration.go +++ b/pkg/command/migration/migration.go @@ -12,9 +12,9 @@ import ( _ "github.com/lib/pq" "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/runner" - "github.com/version-1/gooo/pkg/db" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/logger" + "github.com/version-1/gooo/pkg/core/db" + goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/version-1/gooo/pkg/toolkit/logger" ) var _ Runner = (*runner.Yaml)(nil) diff --git a/pkg/core/command/migration/reader/reader.go b/pkg/command/migration/reader/reader.go similarity index 99% rename from pkg/core/command/migration/reader/reader.go rename to pkg/command/migration/reader/reader.go index 6a013b8..581d41c 100644 --- a/pkg/core/command/migration/reader/reader.go +++ b/pkg/command/migration/reader/reader.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/db" + "github.com/version-1/gooo/pkg/core/db" yaml "gopkg.in/yaml.v3" ) diff --git a/pkg/core/command/migration/reader/record.go b/pkg/command/migration/reader/record.go similarity index 97% rename from pkg/core/command/migration/reader/record.go rename to pkg/command/migration/reader/record.go index 664175d..d3c5369 100644 --- a/pkg/core/command/migration/reader/record.go +++ b/pkg/command/migration/reader/record.go @@ -7,7 +7,7 @@ import ( "time" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/db" + "github.com/version-1/gooo/pkg/core/db" ) type Record struct { diff --git a/pkg/core/command/migration/runner/runner.go b/pkg/command/migration/runner/runner.go similarity index 96% rename from pkg/core/command/migration/runner/runner.go rename to pkg/command/migration/runner/runner.go index 04b71b7..b14075f 100644 --- a/pkg/core/command/migration/runner/runner.go +++ b/pkg/command/migration/runner/runner.go @@ -6,8 +6,8 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/reader" - "github.com/version-1/gooo/pkg/db" - "github.com/version-1/gooo/pkg/logger" + "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/toolkit/logger" ) type Base struct { diff --git a/pkg/core/command/migration/runner/yaml.go b/pkg/command/migration/runner/yaml.go similarity index 96% rename from pkg/core/command/migration/runner/yaml.go rename to pkg/command/migration/runner/yaml.go index 67ea5a8..00d272a 100644 --- a/pkg/core/command/migration/runner/yaml.go +++ b/pkg/command/migration/runner/yaml.go @@ -7,7 +7,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/version-1/gooo/pkg/command/migration/adapter/yaml" - "github.com/version-1/gooo/pkg/db" + "github.com/version-1/gooo/pkg/core/db" ) type Yaml struct { diff --git a/pkg/core/command/seeder/runner/helper.go b/pkg/command/seeder/runner/helper.go similarity index 100% rename from pkg/core/command/seeder/runner/helper.go rename to pkg/command/seeder/runner/helper.go diff --git a/pkg/core/command/seeder/runner/template.go b/pkg/command/seeder/runner/template.go similarity index 100% rename from pkg/core/command/seeder/runner/template.go rename to pkg/command/seeder/runner/template.go diff --git a/pkg/core/command/seeder/seeder.go b/pkg/command/seeder/seeder.go similarity index 96% rename from pkg/core/command/seeder/seeder.go rename to pkg/command/seeder/seeder.go index 19e627e..d8f33b8 100644 --- a/pkg/core/command/seeder/seeder.go +++ b/pkg/command/seeder/seeder.go @@ -7,7 +7,7 @@ import ( _ "github.com/lib/pq" "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/logger" + "github.com/version-1/gooo/pkg/toolkit/logger" ) type SeedExecutor struct { From 94890b9b76e4c0fd167b602b435dd534ca64d299 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 07:47:42 -0800 Subject: [PATCH 10/20] Replace existing schema pkg with new one --- examples/core/cmd/app.go | 4 +- pkg/core/schema/collection.go | 89 ---- pkg/core/schema/collection_test.go | 20 - pkg/core/schema/field.go | 175 ------- pkg/core/{schemav2 => schema}/generate.go | 6 +- pkg/core/schema/internal/renderer/helper.go | 32 -- pkg/core/schema/internal/renderer/jsonapi.go | 109 ----- pkg/core/schema/internal/renderer/schema.go | 261 ----------- pkg/core/schema/internal/renderer/shared.go | 100 ---- .../fixtures/test_resource_serialize.json | 72 --- .../fixtures/test_resources_serialize.json | 114 ----- .../schema/internal/schema/generated--like.go | 120 ----- .../schema/internal/schema/generated--post.go | 138 ------ .../internal/schema/generated--profile.go | 120 ----- .../internal/schema/generated--shared.go | 76 ---- .../schema/internal/schema/generated--user.go | 142 ------ .../schema/internal/schema/jsonapi_test.go | 230 ---------- pkg/core/schema/internal/schema/orm_test.go | 77 ---- pkg/core/schema/internal/schema/schema.go | 45 -- pkg/core/schema/internal/template/template.go | 62 --- pkg/core/schema/internal/valuetype/type.go | 164 ------- pkg/core/schema/migration.go | 73 --- .../{schemav2 => schema}/openapi/schema.go | 0 pkg/core/schema/parser.go | 81 ---- pkg/core/schema/parser_test.go | 426 ------------------ pkg/core/schema/schema.go | 273 ----------- .../template/components/entry.go.tmpl | 0 .../template/components/file.go.tmpl | 0 .../template/components/route.go.tmpl | 0 .../template/components/struct.go.tmpl | 0 .../{schemav2 => schema}/template/file.go | 0 .../{schemav2 => schema}/template/format.go | 0 .../{schemav2 => schema}/template/main.go | 2 +- .../template/namespace.go | 0 .../template/partial/partial.go | 0 .../{schemav2 => schema}/template/route.go | 2 +- .../{schemav2 => schema}/template/schema.go | 4 +- 37 files changed, 9 insertions(+), 3008 deletions(-) delete mode 100644 pkg/core/schema/collection.go delete mode 100644 pkg/core/schema/collection_test.go delete mode 100644 pkg/core/schema/field.go rename pkg/core/{schemav2 => schema}/generate.go (86%) delete mode 100644 pkg/core/schema/internal/renderer/helper.go delete mode 100644 pkg/core/schema/internal/renderer/jsonapi.go delete mode 100644 pkg/core/schema/internal/renderer/schema.go delete mode 100644 pkg/core/schema/internal/renderer/shared.go delete mode 100644 pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json delete mode 100644 pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json delete mode 100644 pkg/core/schema/internal/schema/generated--like.go delete mode 100644 pkg/core/schema/internal/schema/generated--post.go delete mode 100644 pkg/core/schema/internal/schema/generated--profile.go delete mode 100644 pkg/core/schema/internal/schema/generated--shared.go delete mode 100644 pkg/core/schema/internal/schema/generated--user.go delete mode 100644 pkg/core/schema/internal/schema/jsonapi_test.go delete mode 100644 pkg/core/schema/internal/schema/orm_test.go delete mode 100644 pkg/core/schema/internal/schema/schema.go delete mode 100644 pkg/core/schema/internal/template/template.go delete mode 100644 pkg/core/schema/internal/valuetype/type.go delete mode 100644 pkg/core/schema/migration.go rename pkg/core/{schemav2 => schema}/openapi/schema.go (100%) delete mode 100644 pkg/core/schema/parser.go delete mode 100644 pkg/core/schema/parser_test.go delete mode 100644 pkg/core/schema/schema.go rename pkg/core/{schemav2 => schema}/template/components/entry.go.tmpl (100%) rename pkg/core/{schemav2 => schema}/template/components/file.go.tmpl (100%) rename pkg/core/{schemav2 => schema}/template/components/route.go.tmpl (100%) rename pkg/core/{schemav2 => schema}/template/components/struct.go.tmpl (100%) rename pkg/core/{schemav2 => schema}/template/file.go (100%) rename pkg/core/{schemav2 => schema}/template/format.go (100%) rename pkg/core/{schemav2 => schema}/template/main.go (93%) rename pkg/core/{schemav2 => schema}/template/namespace.go (100%) rename pkg/core/{schemav2 => schema}/template/partial/partial.go (100%) rename pkg/core/{schemav2 => schema}/template/route.go (97%) rename pkg/core/{schemav2 => schema}/template/schema.go (96%) diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index a00aec6..9db04d8 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -3,8 +3,8 @@ package main import ( "fmt" - schema "github.com/version-1/gooo/pkg/core/schemav2" - "github.com/version-1/gooo/pkg/core/schemav2/openapi" + schema "github.com/version-1/gooo/pkg/core/schema" + "github.com/version-1/gooo/pkg/core/schema/openapi" ) func main() { diff --git a/pkg/core/schema/collection.go b/pkg/core/schema/collection.go deleted file mode 100644 index e22a47a..0000000 --- a/pkg/core/schema/collection.go +++ /dev/null @@ -1,89 +0,0 @@ -package schema - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/version-1/gooo/pkg/generator" - "github.com/version-1/gooo/pkg/schema/internal/renderer" - "github.com/version-1/gooo/pkg/util" -) - -type SchemaCollection struct { - URL string - Dir string - Package string - Schemas []Schema -} - -func (s SchemaCollection) PackageURL() string { - url := fmt.Sprintf("%s/%s", s.URL, s.Dir) - if strings.HasSuffix(url, "/") { - return url[:len(url)-1] - } - - return url -} - -func (s *SchemaCollection) collect() error { - p := NewParser() - rootPath, err := util.LookupGomodDirPath() - if err != nil { - return err - } - - path := filepath.Clean(fmt.Sprintf("%s/%s/schema.go", rootPath, s.Dir)) - list, err := p.Parse(path) - if err != nil { - return err - } - - s.Schemas = list - - return nil -} - -func (s SchemaCollection) schemaNames() []string { - names := []string{} - for _, schema := range s.Schemas { - names = append(names, schema.Name) - } - return names -} - -func (s SchemaCollection) Gen() error { - if err := s.collect(); err != nil { - return err - } - - t := renderer.NewSharedTemplate(s.Package, s.schemaNames()) - g := generator.Generator{ - Dir: s.Dir, - Template: t, - } - - if err := g.Run(); err != nil { - return err - } - - for _, schema := range s.Schemas { - tmpl := renderer.SchemaTemplate{ - Basename: schema.Name, - URL: s.PackageURL(), - Package: s.Package, - Schema: schema, - } - - g := generator.Generator{ - Dir: s.Dir, - Template: tmpl, - } - - if err := g.Run(); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/core/schema/collection_test.go b/pkg/core/schema/collection_test.go deleted file mode 100644 index 2f2c19a..0000000 --- a/pkg/core/schema/collection_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package schema - -import ( - "path/filepath" - "testing" -) - -func TestSchemaCollection_Gen(t *testing.T) { - dir := "./pkg/schema/internal/schema" - - schemas := SchemaCollection{ - URL: "github.com/version-1/gooo", - Package: filepath.Base(dir), - Dir: dir, - } - - if err := schemas.Gen(); err != nil { - t.Error(err) - } -} diff --git a/pkg/core/schema/field.go b/pkg/core/schema/field.go deleted file mode 100644 index e5fe7f7..0000000 --- a/pkg/core/schema/field.go +++ /dev/null @@ -1,175 +0,0 @@ -package schema - -import ( - "fmt" - "strings" - - "github.com/version-1/gooo/pkg/datasource/orm/validator" - "github.com/version-1/gooo/pkg/schema/internal/valuetype" - gooostrings "github.com/version-1/gooo/pkg/strings" -) - -type Field struct { - Name string - Type valuetype.FieldType - TypeElementExpr string - Tag FieldTag - Association *Association -} - -func (f Field) String() string { - str := "" - field := fmt.Sprintf("\t%s %s", f.Name, f.Type) - str = fmt.Sprintf("%s\n", field) - - return str -} - -func (f Field) ColumnName() string { - return gooostrings.ToSnakeCase(f.Name) -} - -func (f Field) TableType() string { - v, ok := f.Type.(valuetype.FieldValueType) - if ok { - var opt *valuetype.FieldTableOption - if f.Tag.TableType != "" { - opt = &valuetype.FieldTableOption{ - Type: f.Tag.TableType, - } - } - return v.TableType(opt) - } - - return f.Type.String() -} - -func (f Field) IsMutable() bool { - return !f.Tag.Immutable && !f.Tag.Ignore -} - -func (f Field) IsImmutable() bool { - return f.Tag.Immutable && !f.Tag.Ignore -} - -func (f Field) IsAssociation() bool { - return f.Tag.Association -} - -func (f Field) IsSlice() bool { - return valuetype.MaySlice(f.Type) -} - -func (f Field) IsMap() bool { - return valuetype.MayMap(f.Type) -} - -func (f Field) IsRef() bool { - return valuetype.MayRef(f.Type) -} - -func (f Field) AssociationPrimaryKey() string { - if f.Association == nil { - return "" - } - - return f.Association.Schema.PrimaryKey() -} - -type Validator struct { - Fields []string - Validate validator.Validator -} - -type Association struct { - Slice bool - Schema *Schema -} - -type validationKeys string - -const ( - Required validationKeys = "required" - Email validationKeys = "email" - Date validationKeys = "date" - DateTime validationKeys = "datetime" -) - -type FieldTag struct { - Raw []string - PrimaryKey bool - Immutable bool - Ignore bool - Unique bool - Index bool - DefaultValue string - AllowNull bool - Association bool - TableType string - Validators []string -} - -func parseTag(tag string) FieldTag { - if len(tag) < 2 { - return FieldTag{} - } - tags := findGoooTag(tag[1 : len(tag)-1]) - options := FieldTag{ - Raw: tags, - } - for _, t := range tags { - switch t { - case "primary_key": - options.PrimaryKey = true - case "immutable": - options.Immutable = true - case "unique": - options.Unique = true - case "ignore": - options.Ignore = true - case "index": - options.Index = true - case "association": - options.Association = true - case "allow_null": - options.AllowNull = true - } - - if strings.HasPrefix(t, "type=") { - segments := strings.Split(t, "=") - if len(segments) > 1 { - options.TableType = segments[1] - } - } - - if strings.HasPrefix(t, "default=") { - segments := strings.Split(t, "=") - if len(segments) > 1 { - options.DefaultValue = segments[1] - } - } - - if strings.HasPrefix(t, "validation=") { - segments := strings.Split(t, "=") - if len(segments) > 1 { - options.Validators = strings.Split(segments[1], "/") - } - } - } - - return options -} - -func findGoooTag(s string) []string { - tags := strings.Split(s, " ") - for _, t := range tags { - parts := strings.Split(t, ":") - if len(parts) > 1 { - if parts[0] == "gooo" && len(parts[1]) > 2 { - return strings.Split(parts[1][1:len(parts[1])-1], ",") - } - } - } - - return []string{} -} diff --git a/pkg/core/schemav2/generate.go b/pkg/core/schema/generate.go similarity index 86% rename from pkg/core/schemav2/generate.go rename to pkg/core/schema/generate.go index 47e50ee..7c24066 100644 --- a/pkg/core/schemav2/generate.go +++ b/pkg/core/schema/generate.go @@ -1,12 +1,12 @@ -package schemav2 +package schema import ( "fmt" "path/filepath" "github.com/version-1/gooo/pkg/core/generator" - "github.com/version-1/gooo/pkg/core/schemav2/openapi" - "github.com/version-1/gooo/pkg/core/schemav2/template" + "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/template" ) type Generator struct { diff --git a/pkg/core/schema/internal/renderer/helper.go b/pkg/core/schema/internal/renderer/helper.go deleted file mode 100644 index 2552ebd..0000000 --- a/pkg/core/schema/internal/renderer/helper.go +++ /dev/null @@ -1,32 +0,0 @@ -package renderer - -import ( - "fmt" - "go/format" - - "github.com/version-1/gooo/pkg/errors" - "golang.org/x/tools/imports" -) - -func wrapQuote(list []string) []string { - for i := range list { - list[i] = fmt.Sprintf("\"%s\"", list[i]) - } - - return list -} - -func pretify(filename, s string) (string, error) { - // return s, nil - formatted, err := format.Source([]byte(s)) - if err != nil { - return s, errors.Wrap(err) - } - - processed, err := imports.Process(filename, formatted, nil) - if err != nil { - return string(formatted), errors.Wrap(err) - } - - return string(processed), nil -} diff --git a/pkg/core/schema/internal/renderer/jsonapi.go b/pkg/core/schema/internal/renderer/jsonapi.go deleted file mode 100644 index ed8c061..0000000 --- a/pkg/core/schema/internal/renderer/jsonapi.go +++ /dev/null @@ -1,109 +0,0 @@ -package renderer - -import ( - "fmt" - "strings" - - "github.com/version-1/gooo/pkg/schema/internal/template" - gooostrings "github.com/version-1/gooo/pkg/strings" -) - -func (s SchemaTemplate) defineToJSONAPIResource() string { - primaryKey := s.Schema.PrimaryKey() - - str := fmt.Sprintf(`includes := &jsonapi.Resources{ShouldSort: true} - r := &jsonapi.Resource{ - ID: jsonapi.Stringify(obj.%s), - Type: "%s", - Attributes: obj, - Relationships: jsonapi.Relationships{}, - } - `, primaryKey, gooostrings.ToSnakeCase(s.Schema.GetName())) - str += "\n" - - for _, ident := range s.Schema.AssociationFieldIdents() { - if ident.Slice { - str += fmt.Sprintf( - `elements := []jsonapi.Resourcer{} - for _, ele := range obj.%s { - elements = append(elements, jsonapi.Resourcer(ele)) - } - jsonapi.HasMany(r, includes, elements, "%s", func(ri *jsonapi.ResourceIdentifier, i int) { - id := obj.%s[i].%s - ri.ID = jsonapi.Stringify(id) - })`, - ident.FieldName, - ident.TypeName, - ident.FieldName, - ident.PrimaryKey, - ) - str += "\n" - } else { - if ident.Ref { - str += fmt.Sprintf( - `ele := obj.%s - if ele != nil { - jsonapi.HasOne(r, includes, ele, ele.%s, "%s") - }`, - ident.FieldName, - ident.PrimaryKey, - ident.TypeName, - ) - } else { - str += fmt.Sprintf( - `ele := obj.%s - if ele.%s != (%s{}).%s { - jsonapi.HasOne(r, includes, ele, ele.%s, "%s") - }`, - ident.FieldName, - ident.PrimaryKey, - ident.TypeElementExpr, - ident.PrimaryKey, - ident.PrimaryKey, - ident.TypeName, - ) - } - str += "\n" - } - str += "\n" - } - - str += "\n" - str += "return *r, *includes" - - return template.Method{ - Receiver: s.Schema.GetName(), - Name: "ToJSONAPIResource", - Args: []template.Arg{}, - ReturnTypes: []string{"jsonapi.Resource", "jsonapi.Resources"}, - Body: str, - }.String() -} - -func (s SchemaTemplate) defineJSONAPISerialize() string { - fields := []string{} - for _, n := range s.Schema.AttributeFieldNames() { - v := fmt.Sprintf( - `fmt.Sprintf("\"%s\": %s", jsonapi.MustEscape(obj.%s))`, - gooostrings.ToSnakeCase(n), - "%s", - n, - ) - fields = append( - fields, - v, - ) - } - str := "lines := []string{\n" - str += strings.Join(fields, ", \n") + ",\n" - str += "}\n" - str += "return fmt.Sprintf(\"{\\n%s\\n}\", strings.Join(lines, \", \\n\")), nil" - - return template.Method{ - Receiver: s.Schema.GetName(), - Name: "JSONAPISerialize", - Args: []template.Arg{}, - ReturnTypes: []string{"string", "error"}, - Body: str, - }.String() -} diff --git a/pkg/core/schema/internal/renderer/schema.go b/pkg/core/schema/internal/renderer/schema.go deleted file mode 100644 index 34b23a1..0000000 --- a/pkg/core/schema/internal/renderer/schema.go +++ /dev/null @@ -1,261 +0,0 @@ -package renderer - -import ( - "fmt" - "strings" - - "github.com/version-1/gooo/pkg/schema/internal/template" - "github.com/version-1/gooo/pkg/util" -) - -const GeneratedFilePrefix = "generated--" - -var errorsPackage = fmt.Sprintf("goooerrors \"%s\"", "github.com/version-1/gooo/pkg/errors") -var ormerrPackage = fmt.Sprintf("ormerrors \"%s\"", "github.com/version-1/gooo/pkg/datasource/orm/errors") -var schemaPackage = "\"github.com/version-1/gooo/pkg/schema\"" -var utilPackage = "\"github.com/version-1/gooo/pkg/util\"" -var stringsPackage = "gooostrings \"github.com/version-1/gooo/pkg/strings\"" -var jsonapiPackage = "\"github.com/version-1/gooo/pkg/presenter/jsonapi\"" - -type AssociationIdent struct { - FieldName string - PrimaryKey string - TypeElementExpr string - TypeName string - Slice bool - Ref bool -} - -type schema interface { - GetName() string - GetTableName() string - FieldNames() []string - AttributeFieldNames() []string - MutableColumns() []string - MutableFieldNames() []string - AssociationFieldIdents() []AssociationIdent - PrimaryKey() string - Columns() []string - ColumnFieldNames() []string - SetClause() []string -} - -type SchemaTemplate struct { - Basename string - URL string - Package string - Schema schema -} - -func (s SchemaTemplate) Filename() string { - return fmt.Sprintf("generated--%s", util.Basename(strings.ToLower(s.Basename))) -} - -func (s SchemaTemplate) Render() (string, error) { - str := "" - str += fmt.Sprintf("package %s\n", s.Package) - str += "\n" - - if len(s.libs()) > 0 { - str += fmt.Sprintf("import (\n%s\n)\n", strings.Join(s.libs(), "\n")) - } - str += "\n" - - // columns - str += template.Method{ - Receiver: s.Schema.GetName(), - Name: "Columns", - Args: []template.Arg{}, - ReturnTypes: []string{"[]string"}, - Body: fmt.Sprintf( - "return []string{%s}", - strings.Join(wrapQuote(s.Schema.Columns()), ", "), - ), - }.String() - - // scan - scanFields := []string{} - for _, n := range s.Schema.ColumnFieldNames() { - scanFields = append(scanFields, fmt.Sprintf("&obj.%s", n)) - } - - receiver := template.Pointer(s.Schema.GetName()) - methods := []template.Method{ - { - Receiver: receiver, - Name: "Scan", - Args: []template.Arg{ - {Name: "rows", Type: "scanner"}, - }, - ReturnTypes: []string{"error"}, - Body: fmt.Sprintf(`if err := rows.Scan(%s); err != nil { - return err - } - - return nil`, - strings.Join(scanFields, ", "), - ), - }, - { - Receiver: receiver, - Name: "Destroy", - Args: []template.Arg{ - {Name: "ctx", Type: "context.Context"}, - {Name: "qr", Type: "queryer"}, - }, - ReturnTypes: []string{"error"}, - Body: fmt.Sprintf(`zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "DELETE FROM %s WHERE id = $1" - if _, err := qr.ExecContext(ctx, query, obj.ID); err != nil { - return goooerrors.Wrap(err) - } - - return nil`, s.Schema.GetTableName()), - }, - { - Receiver: receiver, - Name: "Find", - Args: []template.Arg{ - {Name: "ctx", Type: "context.Context"}, - {Name: "qr", Type: "queryer"}, - }, - ReturnTypes: []string{"error"}, - Body: fmt.Sprintf(`zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "SELECT %s FROM %s WHERE id = $1" - row := qr.QueryRowContext(ctx, query, obj.ID) - - if err := obj.Scan(row); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return goooerrors.Wrap(ErrNotFound) - } - - return goooerrors.Wrap(err) - } - - return nil`, - strings.Join(s.Schema.Columns(), ", "), - s.Schema.GetTableName(), - ), - }, - } - - for _, m := range methods { - str += m.String() - } - - str += s.defineSave() - str += s.defineAssign() - str += s.defineValidate() - str += s.defineJSONAPISerialize() - str += s.defineToJSONAPIResource() - - return pretify(s.Filename(), str) -} - -func (s SchemaTemplate) defineValidate() string { - str := "" - str += "return nil" - - return template.Method{ - Receiver: s.Schema.GetName(), - Name: "validate", - Args: []template.Arg{}, - ReturnTypes: []string{"ormerrors.ValidationError"}, - Body: str, - }.String() -} - -func (s SchemaTemplate) defineSave() string { - query := fmt.Sprintf(` - INSERT INTO %s (%s) VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET %s - RETURNING %s - `, - s.Schema.GetTableName(), - strings.Join(s.Schema.MutableColumns(), ", "), - strings.Join(s.Schema.SetClause(), ", "), - strings.Join(s.Schema.Columns(), ", "), - ) - - mutableValues := []string{} - for _, n := range s.Schema.MutableFieldNames() { - mutableValues = append(mutableValues, fmt.Sprintf("obj.%s", n)) - } - - validateStr := `if err := obj.validate(); err != nil { - return err - } - ` - - return template.Method{ - Receiver: template.Pointer(s.Schema.GetName()), - Name: "Save", - Args: []template.Arg{ - {Name: "ctx", Type: "context.Context"}, - {Name: "qr", Type: "queryer"}, - }, - ReturnTypes: []string{"error"}, - Body: fmt.Sprintf( - validateStr+ - "query := `%s`\n"+` - row := qr.QueryRowContext(ctx, query, %s) - if err := obj.Scan(row); err != nil { - return err - } - - return nil`, - query, - strings.Join(mutableValues, ", "), - ), - }.String() -} - -func (s SchemaTemplate) defineAssign() string { - fields := []string{} - for _, n := range s.Schema.FieldNames() { - fields = append(fields, fmt.Sprintf("obj.%s = v.%s", n, n)) - } - - return template.Method{ - Receiver: template.Pointer(s.Schema.GetName()), - Name: "Assign", - Args: []template.Arg{ - {Name: "v", Type: s.Schema.GetName()}, - }, - ReturnTypes: []string{}, - Body: strings.Join(fields, "\n"), - }.String() -} - -func (s SchemaTemplate) libs() []string { - list := []string{ - schemaPackage, - errorsPackage, - ormerrPackage, - stringsPackage, - jsonapiPackage, - utilPackage, - "\"github.com/google/uuid\"", - "\"strings\"", - "\"time\"", - "\"fmt\"", - } - - return list -} diff --git a/pkg/core/schema/internal/renderer/shared.go b/pkg/core/schema/internal/renderer/shared.go deleted file mode 100644 index f79e676..0000000 --- a/pkg/core/schema/internal/renderer/shared.go +++ /dev/null @@ -1,100 +0,0 @@ -package renderer - -import ( - "fmt" - "strings" - - "github.com/version-1/gooo/pkg/schema/internal/template" -) - -type SharedTemplate struct { - pkg string - schemaNames []string -} - -func NewSharedTemplate(pkg string, schemaNames []string) *SharedTemplate { - return &SharedTemplate{ - pkg: pkg, - schemaNames: schemaNames, - } -} - -func (s SharedTemplate) Filename() string { - return fmt.Sprintf("%s%s", GeneratedFilePrefix, "shared") -} - -func (s SharedTemplate) Render() (string, error) { - str := "" - str += fmt.Sprintf("package %s\n", s.pkg) - str += "\n" - str += "// this file is generated by gooo ORM. DON'T EDIT this file\n" - - sharedLibs := []string{ - "\"context\"", - "\"database/sql\"", - errorsPackage, - } - - if len(sharedLibs) > 0 { - str += fmt.Sprintf("import (\n%s\n)\n", strings.Join(sharedLibs, "\n")) - } - str += "\n" - - str += template.Interface{ - Name: "scanner", - Inters: []string{ - "Scan(dest ...any) error", - }, - }.String() - - str += template.Interface{ - Name: "queryer", - Inters: []string{ - "QueryRowContext(ctx context.Context, query string, dest ...any) *sql.Row", - "QueryContext(ctx context.Context, query string, dest ...any) (*sql.Rows, error)", - "ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)", - }, - }.String() - - str += "\n" - - // errors - str += `type NotFoundError struct {} - - func (e NotFoundError) Error() string { - return "record not found" - } - - var ErrNotFound = NotFoundError{}` - - str += "\n" - - str += `type PrimaryKeyMissingError struct {} - - func (e PrimaryKeyMissingError) Error() string { - return "primary key is required" - } - - var ErrPrimaryKeyMissing = PrimaryKeyMissingError{}` - - str += "\n" - - for _, name := range s.schemaNames { - str += fmt.Sprintf(`func New%s() *%s { - return &%s{} - } - `, name, name, name) - str += "\n" - - str += fmt.Sprintf(`func New%sWith(obj %s) *%s { - m := &%s{} - m.Assign(obj) - - return m - } - `, name, name, name, name) - str += "\n" - } - - return pretify(s.Filename(), str) -} diff --git a/pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json b/pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json deleted file mode 100644 index 73082ab..0000000 --- a/pkg/core/schema/internal/schema/fixtures/test_resource_serialize.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "data": { - "id": "1", - "type": "user", - "attributes": { - "username": "test", - "email": "test@example.com", - "refresh_token": "refresh_token", - "timezone": "Asia/Tokyo", - "time_diff": 9, - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships": { - "post": { - "data": [ - { - "id": "10", - "type": "post" - }, - { - "id": "11", - "type": "post" - } - ] - } - } - }, - "included": [ - { - "id": "10", - "type": "post", - "attributes": { - "user_id": 1, - "title": "title1", - "body": "body1", - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships":{ - "user":{"data":{"id":"1","type":"user"}} - } - }, - { - "id": "11", - "type": "post", - "attributes": { - "user_id": 1, - "title": "title2", - "body": "body2", - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships":{ - "user":{"data":{"id":"1","type":"user"}} - } - }, - { - "id":"1", - "type":"user", - "attributes": { - "username":"test", - "email":"test@example.com", - "refresh_token":"refresh_token", - "timezone":"Asia/Tokyo", - "time_diff":9, - "created_at":"2024-08-07T01:58:13Z", - "updated_at":"2024-08-07T01:58:13Z" - } - } - ] -} diff --git a/pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json b/pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json deleted file mode 100644 index fad0dca..0000000 --- a/pkg/core/schema/internal/schema/fixtures/test_resources_serialize.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "data": [ - { - "id": "1", - "type": "user", - "attributes": { - "username": "test0", - "email": "test0@example.com", - "refresh_token": "", - "timezone": "", - "time_diff": 0, - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships": { - "post": { - "data": [ - { - "id": "4", - "type": "post" - } - ] - } - } - }, - { - "id": "2", - "type": "user", - "attributes": { - "username": "test1", - "email": "test1@example.com", - "refresh_token": "", - "timezone": "", - "time_diff": 0, - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships": { - "post": { - "data": [ - { - "id": "5", - "type": "post" - } - ] - } - } - }, - { - "id": "3", - "type": "user", - "attributes": { - "username": "test2", - "email": "test2@example.com", - "refresh_token": "", - "timezone": "", - "time_diff": 0, - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - }, - "relationships": { - "post": { - "data": [ - { - "id": "6", - "type": "post" - } - ] - } - } - } - ], - "meta": { - "has_next": true, - "has_prev": true, - "page": 1, - "total": 3 - }, - "included": [ - { - "id": "4", - "type": "post", - "attributes": { - "user_id": 1, - "title": "title0", - "body": "body0", - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - } - }, - { - "id": "5", - "type": "post", - "attributes": { - "user_id": 2, - "title": "title1", - "body": "body1", - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - } - }, - { - "id": "6", - "type": "post", - "attributes": { - "user_id": 3, - "title": "title2", - "body": "body2", - "created_at": "2024-08-07T01:58:13Z", - "updated_at": "2024-08-07T01:58:13Z" - } - } - ] -} diff --git a/pkg/core/schema/internal/schema/generated--like.go b/pkg/core/schema/internal/schema/generated--like.go deleted file mode 100644 index 684ebe1..0000000 --- a/pkg/core/schema/internal/schema/generated--like.go +++ /dev/null @@ -1,120 +0,0 @@ -package fixtures - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - ormerrors "github.com/version-1/gooo/pkg/datasource/orm/errors" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" - "github.com/version-1/gooo/pkg/util" -) - -func (obj Like) Columns() []string { - return []string{"id", "likeable_id", "likeable_type", "created_at", "updated_at"} -} - -func (obj *Like) Scan(rows scanner) error { - if err := rows.Scan(&obj.ID, &obj.LikeableID, &obj.LikeableType, &obj.CreatedAt, &obj.UpdatedAt); err != nil { - return err - } - - return nil -} - -func (obj *Like) Destroy(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "DELETE FROM likes WHERE id = $1" - if _, err := qr.ExecContext(ctx, query, obj.ID); err != nil { - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Like) Find(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "SELECT id, likeable_id, likeable_type, created_at, updated_at FROM likes WHERE id = $1" - row := qr.QueryRowContext(ctx, query, obj.ID) - - if err := obj.Scan(row); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return goooerrors.Wrap(ErrNotFound) - } - - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Like) Save(ctx context.Context, qr queryer) error { - if err := obj.validate(); err != nil { - return err - } - query := ` - INSERT INTO likes (likeable_id, likeable_type) VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET likeable_id = $1, likeable_type = $2, updated_at = NOW() - RETURNING id, likeable_id, likeable_type, created_at, updated_at - ` - - row := qr.QueryRowContext(ctx, query, obj.LikeableID, obj.LikeableType) - if err := obj.Scan(row); err != nil { - return err - } - - return nil -} - -func (obj *Like) Assign(v Like) { - obj.ID = v.ID - obj.LikeableID = v.LikeableID - obj.LikeableType = v.LikeableType - obj.CreatedAt = v.CreatedAt - obj.UpdatedAt = v.UpdatedAt -} - -func (obj Like) validate() ormerrors.ValidationError { - return nil -} - -func (obj Like) JSONAPISerialize() (string, error) { - lines := []string{ - fmt.Sprintf("\"likeable_id\": %s", jsonapi.MustEscape(obj.LikeableID)), - fmt.Sprintf("\"likeable_type\": %s", jsonapi.MustEscape(obj.LikeableType)), - fmt.Sprintf("\"created_at\": %s", jsonapi.MustEscape(obj.CreatedAt)), - fmt.Sprintf("\"updated_at\": %s", jsonapi.MustEscape(obj.UpdatedAt)), - } - return fmt.Sprintf("{\n%s\n}", strings.Join(lines, ", \n")), nil -} - -func (obj Like) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - includes := &jsonapi.Resources{ShouldSort: true} - r := &jsonapi.Resource{ - ID: jsonapi.Stringify(obj.ID), - Type: "like", - Attributes: obj, - Relationships: jsonapi.Relationships{}, - } - - return *r, *includes -} diff --git a/pkg/core/schema/internal/schema/generated--post.go b/pkg/core/schema/internal/schema/generated--post.go deleted file mode 100644 index d50ade0..0000000 --- a/pkg/core/schema/internal/schema/generated--post.go +++ /dev/null @@ -1,138 +0,0 @@ -package fixtures - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - ormerrors "github.com/version-1/gooo/pkg/datasource/orm/errors" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" - "github.com/version-1/gooo/pkg/util" -) - -func (obj Post) Columns() []string { - return []string{"id", "user_id", "title", "body", "created_at", "updated_at"} -} - -func (obj *Post) Scan(rows scanner) error { - if err := rows.Scan(&obj.ID, &obj.UserID, &obj.Title, &obj.Body, &obj.CreatedAt, &obj.UpdatedAt); err != nil { - return err - } - - return nil -} - -func (obj *Post) Destroy(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "DELETE FROM posts WHERE id = $1" - if _, err := qr.ExecContext(ctx, query, obj.ID); err != nil { - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Post) Find(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "SELECT id, user_id, title, body, created_at, updated_at FROM posts WHERE id = $1" - row := qr.QueryRowContext(ctx, query, obj.ID) - - if err := obj.Scan(row); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return goooerrors.Wrap(ErrNotFound) - } - - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Post) Save(ctx context.Context, qr queryer) error { - if err := obj.validate(); err != nil { - return err - } - query := ` - INSERT INTO posts (user_id, title, body, user, likes) VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET user_id = $1, title = $2, body = $3, user = $4, likes = $5, updated_at = NOW() - RETURNING id, user_id, title, body, created_at, updated_at - ` - - row := qr.QueryRowContext(ctx, query, obj.UserID, obj.Title, obj.Body, obj.User, obj.Likes) - if err := obj.Scan(row); err != nil { - return err - } - - return nil -} - -func (obj *Post) Assign(v Post) { - obj.ID = v.ID - obj.UserID = v.UserID - obj.Title = v.Title - obj.Body = v.Body - obj.CreatedAt = v.CreatedAt - obj.UpdatedAt = v.UpdatedAt - obj.User = v.User - obj.Likes = v.Likes -} - -func (obj Post) validate() ormerrors.ValidationError { - return nil -} - -func (obj Post) JSONAPISerialize() (string, error) { - lines := []string{ - fmt.Sprintf("\"user_id\": %s", jsonapi.MustEscape(obj.UserID)), - fmt.Sprintf("\"title\": %s", jsonapi.MustEscape(obj.Title)), - fmt.Sprintf("\"body\": %s", jsonapi.MustEscape(obj.Body)), - fmt.Sprintf("\"created_at\": %s", jsonapi.MustEscape(obj.CreatedAt)), - fmt.Sprintf("\"updated_at\": %s", jsonapi.MustEscape(obj.UpdatedAt)), - } - return fmt.Sprintf("{\n%s\n}", strings.Join(lines, ", \n")), nil -} - -func (obj Post) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - includes := &jsonapi.Resources{ShouldSort: true} - r := &jsonapi.Resource{ - ID: jsonapi.Stringify(obj.ID), - Type: "post", - Attributes: obj, - Relationships: jsonapi.Relationships{}, - } - - ele := obj.User - if ele.ID != (User{}).ID { - jsonapi.HasOne(r, includes, ele, ele.ID, "user") - } - - elements := []jsonapi.Resourcer{} - for _, ele := range obj.Likes { - elements = append(elements, jsonapi.Resourcer(ele)) - } - jsonapi.HasMany(r, includes, elements, "like", func(ri *jsonapi.ResourceIdentifier, i int) { - id := obj.Likes[i].ID - ri.ID = jsonapi.Stringify(id) - }) - - return *r, *includes -} diff --git a/pkg/core/schema/internal/schema/generated--profile.go b/pkg/core/schema/internal/schema/generated--profile.go deleted file mode 100644 index f90c92c..0000000 --- a/pkg/core/schema/internal/schema/generated--profile.go +++ /dev/null @@ -1,120 +0,0 @@ -package fixtures - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - ormerrors "github.com/version-1/gooo/pkg/datasource/orm/errors" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" - "github.com/version-1/gooo/pkg/util" -) - -func (obj Profile) Columns() []string { - return []string{"id", "user_id", "bio", "created_at", "updated_at"} -} - -func (obj *Profile) Scan(rows scanner) error { - if err := rows.Scan(&obj.ID, &obj.UserID, &obj.Bio, &obj.CreatedAt, &obj.UpdatedAt); err != nil { - return err - } - - return nil -} - -func (obj *Profile) Destroy(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "DELETE FROM profiles WHERE id = $1" - if _, err := qr.ExecContext(ctx, query, obj.ID); err != nil { - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Profile) Find(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "SELECT id, user_id, bio, created_at, updated_at FROM profiles WHERE id = $1" - row := qr.QueryRowContext(ctx, query, obj.ID) - - if err := obj.Scan(row); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return goooerrors.Wrap(ErrNotFound) - } - - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *Profile) Save(ctx context.Context, qr queryer) error { - if err := obj.validate(); err != nil { - return err - } - query := ` - INSERT INTO profiles (user_id, bio) VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET user_id = $1, bio = $2, updated_at = NOW() - RETURNING id, user_id, bio, created_at, updated_at - ` - - row := qr.QueryRowContext(ctx, query, obj.UserID, obj.Bio) - if err := obj.Scan(row); err != nil { - return err - } - - return nil -} - -func (obj *Profile) Assign(v Profile) { - obj.ID = v.ID - obj.UserID = v.UserID - obj.Bio = v.Bio - obj.CreatedAt = v.CreatedAt - obj.UpdatedAt = v.UpdatedAt -} - -func (obj Profile) validate() ormerrors.ValidationError { - return nil -} - -func (obj Profile) JSONAPISerialize() (string, error) { - lines := []string{ - fmt.Sprintf("\"user_id\": %s", jsonapi.MustEscape(obj.UserID)), - fmt.Sprintf("\"bio\": %s", jsonapi.MustEscape(obj.Bio)), - fmt.Sprintf("\"created_at\": %s", jsonapi.MustEscape(obj.CreatedAt)), - fmt.Sprintf("\"updated_at\": %s", jsonapi.MustEscape(obj.UpdatedAt)), - } - return fmt.Sprintf("{\n%s\n}", strings.Join(lines, ", \n")), nil -} - -func (obj Profile) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - includes := &jsonapi.Resources{ShouldSort: true} - r := &jsonapi.Resource{ - ID: jsonapi.Stringify(obj.ID), - Type: "profile", - Attributes: obj, - Relationships: jsonapi.Relationships{}, - } - - return *r, *includes -} diff --git a/pkg/core/schema/internal/schema/generated--shared.go b/pkg/core/schema/internal/schema/generated--shared.go deleted file mode 100644 index be1a86f..0000000 --- a/pkg/core/schema/internal/schema/generated--shared.go +++ /dev/null @@ -1,76 +0,0 @@ -package fixtures - -// this file is generated by gooo ORM. DON'T EDIT this file -import ( - "context" - "database/sql" -) - -type scanner interface { - Scan(dest ...any) error -} -type queryer interface { - QueryRowContext(ctx context.Context, query string, dest ...any) *sql.Row - QueryContext(ctx context.Context, query string, dest ...any) (*sql.Rows, error) - ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) -} - -type NotFoundError struct{} - -func (e NotFoundError) Error() string { - return "record not found" -} - -var ErrNotFound = NotFoundError{} - -type PrimaryKeyMissingError struct{} - -func (e PrimaryKeyMissingError) Error() string { - return "primary key is required" -} - -var ErrPrimaryKeyMissing = PrimaryKeyMissingError{} - -func NewUser() *User { - return &User{} -} - -func NewUserWith(obj User) *User { - m := &User{} - m.Assign(obj) - - return m -} - -func NewPost() *Post { - return &Post{} -} - -func NewPostWith(obj Post) *Post { - m := &Post{} - m.Assign(obj) - - return m -} - -func NewProfile() *Profile { - return &Profile{} -} - -func NewProfileWith(obj Profile) *Profile { - m := &Profile{} - m.Assign(obj) - - return m -} - -func NewLike() *Like { - return &Like{} -} - -func NewLikeWith(obj Like) *Like { - m := &Like{} - m.Assign(obj) - - return m -} diff --git a/pkg/core/schema/internal/schema/generated--user.go b/pkg/core/schema/internal/schema/generated--user.go deleted file mode 100644 index e471422..0000000 --- a/pkg/core/schema/internal/schema/generated--user.go +++ /dev/null @@ -1,142 +0,0 @@ -package fixtures - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - ormerrors "github.com/version-1/gooo/pkg/datasource/orm/errors" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/presenter/jsonapi" - "github.com/version-1/gooo/pkg/util" -) - -func (obj User) Columns() []string { - return []string{"id", "username", "email", "refresh_token", "timezone", "time_diff", "created_at", "updated_at"} -} - -func (obj *User) Scan(rows scanner) error { - if err := rows.Scan(&obj.ID, &obj.Username, &obj.Email, &obj.RefreshToken, &obj.Timezone, &obj.TimeDiff, &obj.CreatedAt, &obj.UpdatedAt); err != nil { - return err - } - - return nil -} - -func (obj *User) Destroy(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "DELETE FROM users WHERE id = $1" - if _, err := qr.ExecContext(ctx, query, obj.ID); err != nil { - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *User) Find(ctx context.Context, qr queryer) error { - zero, err := util.IsZero(obj.ID) - if err != nil { - return goooerrors.Wrap(err) - } - - if zero { - return goooerrors.Wrap(ErrPrimaryKeyMissing) - } - - query := "SELECT id, username, email, refresh_token, timezone, time_diff, created_at, updated_at FROM users WHERE id = $1" - row := qr.QueryRowContext(ctx, query, obj.ID) - - if err := obj.Scan(row); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return goooerrors.Wrap(ErrNotFound) - } - - return goooerrors.Wrap(err) - } - - return nil -} - -func (obj *User) Save(ctx context.Context, qr queryer) error { - if err := obj.validate(); err != nil { - return err - } - query := ` - INSERT INTO users (username, email, refresh_token, timezone, time_diff, profile, posts) VALUES ($1, $2, $3) - ON CONFLICT(id) DO UPDATE SET username = $1, email = $2, refresh_token = $3, timezone = $4, time_diff = $5, profile = $6, posts = $7, updated_at = NOW() - RETURNING id, username, email, refresh_token, timezone, time_diff, created_at, updated_at - ` - - row := qr.QueryRowContext(ctx, query, obj.Username, obj.Email, obj.RefreshToken, obj.Timezone, obj.TimeDiff, obj.Profile, obj.Posts) - if err := obj.Scan(row); err != nil { - return err - } - - return nil -} - -func (obj *User) Assign(v User) { - obj.ID = v.ID - obj.Username = v.Username - obj.Email = v.Email - obj.RefreshToken = v.RefreshToken - obj.Timezone = v.Timezone - obj.TimeDiff = v.TimeDiff - obj.CreatedAt = v.CreatedAt - obj.UpdatedAt = v.UpdatedAt - obj.Profile = v.Profile - obj.Posts = v.Posts -} - -func (obj User) validate() ormerrors.ValidationError { - return nil -} - -func (obj User) JSONAPISerialize() (string, error) { - lines := []string{ - fmt.Sprintf("\"username\": %s", jsonapi.MustEscape(obj.Username)), - fmt.Sprintf("\"email\": %s", jsonapi.MustEscape(obj.Email)), - fmt.Sprintf("\"refresh_token\": %s", jsonapi.MustEscape(obj.RefreshToken)), - fmt.Sprintf("\"timezone\": %s", jsonapi.MustEscape(obj.Timezone)), - fmt.Sprintf("\"time_diff\": %s", jsonapi.MustEscape(obj.TimeDiff)), - fmt.Sprintf("\"created_at\": %s", jsonapi.MustEscape(obj.CreatedAt)), - fmt.Sprintf("\"updated_at\": %s", jsonapi.MustEscape(obj.UpdatedAt)), - } - return fmt.Sprintf("{\n%s\n}", strings.Join(lines, ", \n")), nil -} - -func (obj User) ToJSONAPIResource() (jsonapi.Resource, jsonapi.Resources) { - includes := &jsonapi.Resources{ShouldSort: true} - r := &jsonapi.Resource{ - ID: jsonapi.Stringify(obj.ID), - Type: "user", - Attributes: obj, - Relationships: jsonapi.Relationships{}, - } - - ele := obj.Profile - if ele != nil { - jsonapi.HasOne(r, includes, ele, ele.ID, "profile") - } - - elements := []jsonapi.Resourcer{} - for _, ele := range obj.Posts { - elements = append(elements, jsonapi.Resourcer(ele)) - } - jsonapi.HasMany(r, includes, elements, "post", func(ri *jsonapi.ResourceIdentifier, i int) { - id := obj.Posts[i].ID - ri.ID = jsonapi.Stringify(id) - }) - - return *r, *includes -} diff --git a/pkg/core/schema/internal/schema/jsonapi_test.go b/pkg/core/schema/internal/schema/jsonapi_test.go deleted file mode 100644 index eac2038..0000000 --- a/pkg/core/schema/internal/schema/jsonapi_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package fixtures - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "os" - "strconv" - "strings" - "testing" - "time" - - "github.com/version-1/gooo/pkg/presenter/jsonapi" -) - -type Meta struct { - Total int - Page int - HasNext bool - HasPrev bool -} - -func (m Meta) JSONAPISerialize() (string, error) { - data := map[string]any{ - "total": m.Total, - "page": m.Page, - "has_next": m.HasNext, - "has_prev": m.HasPrev, - } - - b, err := json.Marshal(data) - if err != nil { - return "", err - } - - return string(b), nil -} - -func TestResourcesSerialize(t *testing.T) { - now, err := time.Parse(time.RFC3339, "2024-08-07T01:58:13+00:00") - if err != nil { - t.Fatal(err) - } - - uid := []int{ - 1, - 2, - 3, - } - - postID := []int{ - 4, - 5, - 6, - } - - users := []User{} - for i, id := range uid { - u := NewUser() - u.Assign(User{ - ID: id, - Username: "test" + strconv.Itoa(i), - Email: fmt.Sprintf("test%d@example.com", i), - CreatedAt: now, - UpdatedAt: now, - Posts: []Post{ - { - ID: postID[i], - UserID: id, - Title: "title" + strconv.Itoa(i), - Body: "body" + strconv.Itoa(i), - CreatedAt: now, - UpdatedAt: now, - }, - }, - }) - - users = append(users, *u) - } - root, err := jsonapi.NewManyFrom( - users, - Meta{ - Total: 3, - Page: 1, - HasNext: true, - HasPrev: true, - }, - ) - if err != nil { - t.Fatal(err) - } - - s, err := root.Serialize() - if err != nil { - t.Fatal(err) - } - - expected, err := os.ReadFile("./fixtures/test_resources_serialize.json") - if err != nil { - t.Fatal(err) - } - - buf := &bytes.Buffer{} - if err := json.Compact(buf, expected); err != nil { - t.Fatal(err) - } - - if err := diff(buf.String(), s); err != nil { - fmt.Printf("expect %s\n\n got %s \n\n\n", buf.String(), s) - t.Fatal(err) - } -} - -func TestResourceSerialize(t *testing.T) { - now, err := time.Parse(time.RFC3339, "2024-08-07T01:58:13+00:00") - if err != nil { - t.Fatal(err) - } - - uid := 1 - p1 := 10 - p2 := 11 - u := NewUserWith(User{ - ID: uid, - Username: "test", - Email: "test@example.com", - RefreshToken: "refresh_token", - Timezone: "Asia/Tokyo", - TimeDiff: 9, - CreatedAt: now, - UpdatedAt: now, - Posts: []Post{ - { - ID: p1, - UserID: uid, - Title: "title1", - Body: "body1", - CreatedAt: now, - UpdatedAt: now, - User: User{ - ID: uid, - Username: "test", - Email: "test@example.com", - RefreshToken: "refresh_token", - Timezone: "Asia/Tokyo", - TimeDiff: 9, - CreatedAt: now, - UpdatedAt: now, - }, - }, - { - ID: p2, - UserID: uid, - Title: "title2", - Body: "body2", - CreatedAt: now, - UpdatedAt: now, - User: User{ - ID: uid, - Username: "test", - Email: "test@example.com", - RefreshToken: "refresh_token", - Timezone: "Asia/Tokyo", - TimeDiff: 9, - CreatedAt: now, - UpdatedAt: now, - }, - }, - }, - }) - - resource, includes := u.ToJSONAPIResource() - - root, err := jsonapi.New(resource, includes, nil) - if err != nil { - t.Fatal(err) - } - - s, err := root.Serialize() - if err != nil { - t.Fatal(err) - } - - expected, err := os.ReadFile("./fixtures/test_resource_serialize.json") - if err != nil { - t.Fatal(err) - } - - buf := &bytes.Buffer{} - if err := json.Compact(buf, expected); err != nil { - t.Fatal(err) - } - - if err := diff(buf.String(), s); err != nil { - fmt.Printf("expect %s\n\n got %s\n\n", buf.String(), s) - t.Fatal(err) - } -} - -func diff(expected, got string) error { - line := 1 - for i := 0; i < len(expected); i++ { - if i >= len(got) { - return errors.New(fmt.Sprintf("got diff at %d line %d. expected(%d), but got(%d)", i, line, len(expected), len(got))) - } - - if expected[i] != got[i] { - expectedLines := strings.Split(expected, "\n") - gotLines := strings.Split(got, "\n") - msg := fmt.Sprintf("got diff at %d line %d. expected \"%s\", but got \"%s\"", i, line, string(expected[i]), string(got[i])) - if line > 1 { - msg += fmt.Sprintf(" %s\n", expectedLines[line-1-1]) - } - msg += fmt.Sprintf("- %s\n", expectedLines[line-1]) - if line < len(expectedLines) { - msg += fmt.Sprintf("- %s\n", expectedLines[line]) - } - msg += "\n\n\n" - msg += fmt.Sprintf("+ %s\n", gotLines[line-1]) - return errors.New(msg) - } - - if expected[i] == '\n' { - line++ - } - } - - return nil -} diff --git a/pkg/core/schema/internal/schema/orm_test.go b/pkg/core/schema/internal/schema/orm_test.go deleted file mode 100644 index 055bc40..0000000 --- a/pkg/core/schema/internal/schema/orm_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package fixtures - -import ( - "context" - "errors" - "log" - "os" - "testing" - - _ "github.com/lib/pq" - - "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/datasource/logging" - "github.com/version-1/gooo/pkg/datasource/orm" -) - -func TestTransaction(t *testing.T) { - db, err := sqlx.Connect("postgres", os.Getenv("DATABASE_URL")) - if err != nil { - log.Fatalln(err) - } - - o := orm.New(db, &logging.MockLogger{}, orm.Options{QueryLog: true}) - ctx := context.Background() - - if _, err := o.ExecContext(ctx, "DELETE FROM test_transaction;"); err != nil { - t.Fatal(err) - } - - err = o.Transaction(ctx, func(e *orm.Executor) error { - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - return nil - }) - if err != nil { - t.Fatal(err) - } - - var count int - if err := o.QueryRowContext(ctx, "SELECT count(*) FROM test_transaction;").Scan(&count); err != nil { - t.Fatal(err) - } - - if count != 3 { - t.Fatalf("expected 3, but got %d", count) - } -} - -func TestTransactionRollback(t *testing.T) { - db, err := sqlx.Connect("postgres", os.Getenv("DATABASE_URL")) - if err != nil { - log.Fatalln(err) - } - - o := orm.New(db, &logging.MockLogger{}, orm.Options{QueryLog: true}) - ctx := context.Background() - - if _, err := o.ExecContext(ctx, "DELETE FROM test_transaction;"); err != nil { - t.Fatal(err) - } - - err = o.Transaction(ctx, func(e *orm.Executor) error { - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - e.QueryRowContext(ctx, "INSERT INTO test_transaction (id) VALUES(gen_random_uuid());") - return errors.New("some error") - }) - var count int - if err := o.QueryRowContext(ctx, "SELECT count(*) FROM test_transaction;").Scan(&count); err != nil { - t.Fatal(err) - } - - if count != 0 { - t.Fatalf("expected 0, but got %d", count) - } -} diff --git a/pkg/core/schema/internal/schema/schema.go b/pkg/core/schema/internal/schema/schema.go deleted file mode 100644 index acd1d3b..0000000 --- a/pkg/core/schema/internal/schema/schema.go +++ /dev/null @@ -1,45 +0,0 @@ -package fixtures - -import "time" - -type User struct { - ID int `json:"id" gooo:"primary_key,immutable"` - Username string `json:"username" gooo:"unique"` - Email string `json:"email"` - RefreshToken string `json:"refresh_token"` - Timezone string `json:"timezone"` - TimeDiff int `json:"time_diff"` - CreatedAt time.Time `json:"created_at" gooo:"immutable"` - UpdatedAt time.Time `json:"updated_at" gooo:"immutable"` - - Profile *Profile `json:"profile" gooo:"association"` - Posts []Post `json:"posts" gooo:"association"` -} - -type Post struct { - ID int `json:"id" gooo:"primary_key,immutable"` - UserID int `json:"user_id" gooo:"index"` - Title string `json:"title"` - Body string `json:"body" gooo:"type=text"` - CreatedAt time.Time `json:"created_at" gooo:"immutable"` - UpdatedAt time.Time `json:"updated_at" gooo:"immutable"` - - User User `json:"user" gooo:"association"` - Likes []Like `json:"likes" gooo:"association"` -} - -type Profile struct { - ID int `json:"id" gooo:"primary_key,immutable"` - UserID int `json:"user_id" gooo:"index"` - Bio string `json:"bio" gooo:"type=text"` - CreatedAt time.Time `json:"created_at" gooo:"immutable"` - UpdatedAt time.Time `json:"updated_at" gooo:"immutable"` -} - -type Like struct { - ID int `json:"id" gooo:"primary_key,immutable"` - LikeableID int `json:"likeable_id" gooo:"index"` - LikeableType string `json:"likeable_type" gooo:"index"` - CreatedAt time.Time `json:"created_at" gooo:"immutable"` - UpdatedAt time.Time `json:"updated_at" gooo:"immutable"` -} diff --git a/pkg/core/schema/internal/template/template.go b/pkg/core/schema/internal/template/template.go deleted file mode 100644 index 0f396f7..0000000 --- a/pkg/core/schema/internal/template/template.go +++ /dev/null @@ -1,62 +0,0 @@ -package template - -import ( - "fmt" - "strings" -) - -func Pointer(name string) string { - return "*" + name -} - -type Method struct { - Receiver string - Name string - Args []Arg - ReturnTypes []string - Body string -} - -func (m Method) String() string { - return fmt.Sprintf( - "func (obj %s) %s(%s) (%s) {\n%s\n}\n\n", - m.Receiver, - m.Name, - stringifyArgs(m.Args), - strings.Join(m.ReturnTypes, ", "), - m.Body, - ) -} - -func stringifyArgs(args []Arg) string { - str := []string{} - for _, a := range args { - str = append(str, a.String()) - } - - return strings.Join(str, ", ") -} - -type Arg struct { - Name string - Type string -} - -func (a Arg) String() string { - return fmt.Sprintf("%s %s", a.Name, a.Type) -} - -type Interface struct { - Name string - Inters []string -} - -func (i Interface) String() string { - str := fmt.Sprintf("type %s interface {\n", i.Name) - for _, i := range i.Inters { - str += fmt.Sprintf("\t%s\n", i) - } - str += "}\n" - - return str -} diff --git a/pkg/core/schema/internal/valuetype/type.go b/pkg/core/schema/internal/valuetype/type.go deleted file mode 100644 index 12f6cf3..0000000 --- a/pkg/core/schema/internal/valuetype/type.go +++ /dev/null @@ -1,164 +0,0 @@ -package valuetype - -import ( - "fmt" - "go/ast" -) - -type FieldType fmt.Stringer - -type FieldTableOption struct { - Type string -} - -type Elementer interface { - Element() FieldType -} - -type FieldValueType string - -func (f FieldValueType) String() string { - return string(f) -} - -func (f FieldValueType) TableType(option *FieldTableOption) string { - if option != nil { - return option.Type - } - - switch f { - case String: - return "VARCHAR(255)" - case Int: - return "INT" - case Bool: - return "BOOLEAN" - case Byte: - return "BYTE" - case Time: - return "TIMESTAMP" - case UUID: - return "UUID" - default: - return f.String() - } -} - -const ( - String FieldValueType = "string" - Int FieldValueType = "int" - Bool FieldValueType = "bool" - Byte FieldValueType = "byte" - Time FieldValueType = "time.Time" - UUID FieldValueType = "uuid.UUID" -) - -type TableFieldType string - -type ref struct { - Type FieldType -} - -func (p ref) String() string { - return fmt.Sprintf("*%s", p.Type) -} - -func (p ref) Element() FieldType { - return p.Type -} - -func MayRef(f FieldType) bool { - _, ok := f.(ref) - return ok -} - -func Ref(f FieldType) ref { - return ref{Type: f} -} - -type slice struct { - Type FieldType -} - -func (s slice) String() string { - return fmt.Sprintf("[]%s", s.Type) -} - -func (s slice) Element() FieldType { - return s.Type -} - -func MaySlice(f FieldType) bool { - _, ok := f.(slice) - return ok -} - -func Slice(f FieldType) slice { - return slice{Type: f} -} - -type maptype struct { - Key FieldType - Value FieldType -} - -func (m maptype) String() string { - return fmt.Sprintf("map[%s]%s\n", m.Key, m.Value) -} - -func MayMap(f FieldType) bool { - _, ok := f.(maptype) - return ok -} - -func Map(key, value FieldType) maptype { - return maptype{Key: key, Value: value} -} - -func convertType(s string) FieldValueType { - switch s { - case "string": - return String - case "int": - return Int - case "bool": - return Bool - case "byte": - return Byte - case "time.Time": - return Time - case "uuid.UUID": - return UUID - } - - return FieldValueType(s) -} - -func ResolveTypeName(f ast.Expr) (FieldType, string) { - var typeName FieldType - var typeElementExpr string - switch t := f.(type) { - case *ast.Ident: - typeElementExpr = t.Name - typeName = convertType(typeElementExpr) - case *ast.SelectorExpr: - typeElementExpr = fmt.Sprintf("%s.%s", t.X, t.Sel) - typeName = convertType(typeElementExpr) - case *ast.StarExpr: - tn, te := ResolveTypeName(t.X) - typeElementExpr = te - typeName = Ref(tn) - case *ast.ArrayType: - tn, te := ResolveTypeName(t.Elt) - typeElementExpr = fmt.Sprintf("%s", tn) - typeName = Slice(convertType(te)) - case *ast.MapType: - typeName = Map( - convertType(fmt.Sprintf("%s", t.Key)), - convertType(fmt.Sprintf("%s", t.Value)), - ) - typeElementExpr = typeName.String() - } - - return typeName, typeElementExpr -} diff --git a/pkg/core/schema/migration.go b/pkg/core/schema/migration.go deleted file mode 100644 index 3f5271e..0000000 --- a/pkg/core/schema/migration.go +++ /dev/null @@ -1,73 +0,0 @@ -package schema - -import ( - "fmt" - - "github.com/version-1/gooo/pkg/command/migration/adapter/yaml" -) - -type MigrationConfig struct { - TableNameMapper map[string]string - Indexes map[string][]yaml.Index -} - -func NewMigration(collection SchemaCollection, config MigrationConfig) *Migration { - m := Migration{ - collection: collection, - config: config, - } - - if m.config.Indexes == nil { - m.config.Indexes = map[string][]yaml.Index{} - } - - return &m -} - -type Migration struct { - collection SchemaCollection - config MigrationConfig -} - -func (m Migration) OriginSchema() (yaml.OriginSchema, error) { - schema := yaml.OriginSchema{} - for _, s := range m.collection.Schemas { - columns := []yaml.Column{} - for _, f := range s.Fields { - if f.IsAssociation() { - continue - } - - columns = append(columns, yaml.Column{ - Name: f.ColumnName(), - Type: f.TableType(), - Default: &f.Tag.DefaultValue, - AllowNull: &f.Tag.AllowNull, - PrimaryKey: &f.Tag.PrimaryKey, - }) - } - - indexes := m.config.Indexes[s.Name] - for _, f := range s.Fields { - if !f.IsAssociation() && (f.Tag.Index || f.Tag.Unique) { - indexes = append(indexes, yaml.Index{ - Name: fmt.Sprintf("index_%s_%s", s.TableName, f.ColumnName()), - Columns: []string{f.ColumnName()}, - Unique: &f.Tag.Unique, - }) - } - } - - tableName, ok := m.config.TableNameMapper[s.Name] - if !ok { - tableName = s.TableName - } - schema.Tables = append(schema.Tables, yaml.Table{ - Name: tableName, - Columns: columns, - Indexes: indexes, - }) - } - - return schema, nil -} diff --git a/pkg/core/schemav2/openapi/schema.go b/pkg/core/schema/openapi/schema.go similarity index 100% rename from pkg/core/schemav2/openapi/schema.go rename to pkg/core/schema/openapi/schema.go diff --git a/pkg/core/schema/parser.go b/pkg/core/schema/parser.go deleted file mode 100644 index 3f57d3f..0000000 --- a/pkg/core/schema/parser.go +++ /dev/null @@ -1,81 +0,0 @@ -package schema - -import ( - "go/ast" - "go/token" - "os" - - goparser "go/parser" - - "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/schema/internal/valuetype" - "github.com/version-1/gooo/pkg/strings" -) - -type parser struct{} - -func NewParser() *parser { - return &parser{} -} - -func (p parser) Parse(path string) ([]Schema, error) { - list := []Schema{} - fset := token.NewFileSet() - src, err := os.ReadFile(path) - if err != nil { - return list, errors.Wrap(err) - } - - node, err := goparser.ParseFile(fset, "", src, goparser.ParseComments) - if err != nil { - return list, errors.Wrap(err) - } - - m := map[string]*Schema{} - ast.Inspect(node, func(n ast.Node) bool { - if t, ok := n.(*ast.TypeSpec); ok { - name := t.Name.Name - if len(list) > 0 { - m[list[len(list)-1].Name] = &list[len(list)-1] - } - list = append(list, Schema{ - Name: name, - TableName: strings.ToPlural(name), - }) - } - - if field, ok := n.(*ast.Field); ok { - if field.Tag != nil { - typeName, typeElementExpr := valuetype.ResolveTypeName(field.Type) - list[len(list)-1].AddFields(Field{ - Name: field.Names[0].Name, - Type: typeName, - TypeElementExpr: typeElementExpr, - Tag: parseTag(field.Tag.Value), - }) - } - } - return true - }) - - m[list[len(list)-1].Name] = &list[len(list)-1] - - for i := range list { - for j := range list[i].Fields { - f := list[i].Fields[j] - if f.IsAssociation() { - schema, ok := m[f.TypeElementExpr] - if !ok { - return list, errors.Errorf("schema %s not found on association", f.TypeElementExpr) - } - - list[i].Fields[j].Association = &Association{ - Schema: schema, - Slice: f.IsSlice(), - } - } - } - } - - return list, nil -} diff --git a/pkg/core/schema/parser_test.go b/pkg/core/schema/parser_test.go deleted file mode 100644 index 36d7e36..0000000 --- a/pkg/core/schema/parser_test.go +++ /dev/null @@ -1,426 +0,0 @@ -package schema - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/version-1/gooo/pkg/schema/internal/valuetype" -) - -func TestParser_Parse(t *testing.T) { - p := NewParser() - list, err := p.Parse("./internal/schema/schema.go") - if err != nil { - t.Fatal(err) - } - - profileSchema := &Schema{ - Name: "Profile", - TableName: "profiles", - Fields: []Field{ - { - Name: "ID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"primary_key", "immutable"}, - PrimaryKey: true, - Immutable: true, - }, - }, - { - Name: "UserID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"index"}, - Index: true, - }, - }, - { - Name: "Bio", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{"type=text"}, - TableType: "text", - }, - }, - { - Name: "CreatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "UpdatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - }, - } - - userSchema := Schema{ - Name: "User", - TableName: "users", - Fields: []Field{ - { - Name: "ID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"primary_key", "immutable"}, - PrimaryKey: true, - Immutable: true, - }, - }, - { - Name: "Username", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{"unique"}, - Unique: true, - }, - }, - { - Name: "Email", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "RefreshToken", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "Timezone", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "TimeDiff", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "CreatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "UpdatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - }, - } - - profileField := Field{ - Name: "Profile", - Type: valuetype.Ref(valuetype.FieldValueType("Profile")), - TypeElementExpr: "Profile", - Tag: FieldTag{ - Raw: []string{"association"}, - Association: true, - }, - Association: &Association{ - Slice: false, - Schema: profileSchema, - }, - } - - postsField := Field{ - Name: "Posts", - Type: valuetype.Slice(valuetype.FieldValueType("Post")), - TypeElementExpr: "Post", - Tag: FieldTag{ - Raw: []string{"association"}, - Association: true, - }, - Association: &Association{ - Slice: true, - Schema: &Schema{ - Name: "Post", - TableName: "posts", - Fields: []Field{ - { - Name: "ID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"primary_key", "immutable"}, - PrimaryKey: true, - Immutable: true, - }, - }, - { - Name: "UserID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"index"}, - Index: true, - }, - }, - { - Name: "Title", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "Body", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{"type=text"}, - TableType: "text", - }, - }, - { - Name: "CreatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "UpdatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "User", - Type: valuetype.FieldValueType("User"), - TypeElementExpr: "User", - Tag: FieldTag{ - Raw: []string{"association"}, - Association: true, - }, - Association: &Association{ - Slice: false, - Schema: &Schema{ - Name: "User", - TableName: "users", - Fields: []Field{ - { - Name: "ID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"primary_key", "immutable"}, - PrimaryKey: true, - Immutable: true, - }, - }, - { - Name: "Username", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{"unique"}, - Unique: true, - }, - }, - { - Name: "Email", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "RefreshToken", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "Timezone", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "TimeDiff", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{}, - }, - }, - { - Name: "CreatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "UpdatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "Profile", - Type: valuetype.Ref(valuetype.FieldValueType("Profile")), - TypeElementExpr: "Profile", - Tag: FieldTag{ - Raw: []string{"association"}, - Association: true, - }, - Association: &Association{ - Slice: false, - Schema: profileSchema, - }, - }, - }, - }, - }, - }, - { - Name: "Likes", - Type: valuetype.Slice(valuetype.FieldValueType("Like")), - TypeElementExpr: "Like", - Tag: FieldTag{ - Raw: []string{"association"}, - Association: true, - }, - Association: &Association{ - Slice: true, - Schema: &Schema{ - Name: "Like", - TableName: "likes", - Fields: []Field{ - { - Name: "ID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"primary_key", "immutable"}, - PrimaryKey: true, - Immutable: true, - }, - }, - { - Name: "LikeableID", - Type: valuetype.Int, - TypeElementExpr: "int", - Tag: FieldTag{ - Raw: []string{"index"}, - Index: true, - }, - }, - { - Name: "LikeableType", - Type: valuetype.String, - TypeElementExpr: "string", - Tag: FieldTag{ - Raw: []string{"index"}, - Index: true, - }, - }, - { - Name: "CreatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - { - Name: "UpdatedAt", - Type: valuetype.Time, - TypeElementExpr: "time.Time", - Tag: FieldTag{ - Raw: []string{"immutable"}, - Immutable: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - names := []string{} - for _, s := range list { - names = append(names, s.Name) - } - - if diff := cmp.Diff([]string{"User", "Post", "Profile", "Like"}, names); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } - - actual := list[0:1] - - profile := actual[0].Fields[8] - posts := actual[0].Fields[9] - actual[0].Fields = actual[0].Fields[0:8] - if diff := cmp.Diff(userSchema, actual[0]); diff != "" { - t.Errorf("userSchema mismatch (-want +got):\n%s", diff) - } - - if diff := cmp.Diff(profileField, profile); diff != "" { - t.Errorf("profileField mismatch (-want +got):\n%s", diff) - } - - opt := cmp.FilterValues(func(x, y *Schema) bool { - return x.Name == "User" || y.Name == "User" - }, cmp.Ignore()) - - if diff := cmp.Diff(postsField, posts, opt); diff != "" { - t.Errorf("postsField mismatch (-want +got):\n%s", diff) - } -} diff --git a/pkg/core/schema/schema.go b/pkg/core/schema/schema.go deleted file mode 100644 index de5230c..0000000 --- a/pkg/core/schema/schema.go +++ /dev/null @@ -1,273 +0,0 @@ -package schema - -import ( - "fmt" - - "github.com/version-1/gooo/pkg/schema/internal/renderer" - "github.com/version-1/gooo/pkg/schema/internal/valuetype" - gooostrings "github.com/version-1/gooo/pkg/strings" -) - -type SchemaFactory struct { - Primary Field - DefaultFields []Field -} - -func (d SchemaFactory) NewSchema(fields []Field) *Schema { - s := &Schema{} - s.Fields = []Field{d.Primary} - s.Fields = append(s.Fields, fields...) - s.Fields = append(s.Fields, d.DefaultFields...) - - return s -} - -type Schema struct { - Name string - TableName string - Fields []Field -} - -type SchemaType struct { - typeName string -} - -func (s SchemaType) String() string { - return s.typeName -} - -func (s Schema) GetName() string { - return s.Name -} - -func (s Schema) GetTableName() string { - return s.TableName -} - -func (s *Schema) Type() SchemaType { - return SchemaType{s.Name} -} - -func (s *Schema) AddFields(fields ...Field) { - s.Fields = append(s.Fields, fields...) -} - -func (s Schema) MutableColumns() []string { - fields := []string{} - for i := range s.Fields { - if s.Fields[i].IsMutable() { - fields = append(fields, gooostrings.ToSnakeCase(s.Fields[i].Name)) - } - } - return fields -} - -func (s Schema) MutableFieldNames() []string { - fields := []string{} - for i := range s.Fields { - if s.Fields[i].IsMutable() { - fields = append(fields, s.Fields[i].Name) - } - } - return fields -} - -func (s Schema) ImmutableColumns() []string { - fields := []string{} - for i := range s.Fields { - if s.Fields[i].IsImmutable() { - fields = append(fields, gooostrings.ToSnakeCase(s.Fields[i].Name)) - } - } - - return fields -} - -func (s Schema) SetClause() []string { - placeholders := []string{} - for i, c := range s.MutableColumns() { - placeholders = append(placeholders, fmt.Sprintf("%s = $%d", gooostrings.ToSnakeCase(c), i+1)) - } - - for _, c := range s.ImmutableColumns() { - if c == "updated_at" { - placeholders = append(placeholders, "updated_at = NOW()") - return placeholders - } - } - - return placeholders -} - -func (s *Schema) MutablePlaceholders() []string { - placeholders := []string{} - index := 1 - for i := range s.Fields { - if s.Fields[i].IsMutable() { - placeholders = append(placeholders, fmt.Sprintf("$%d", index)) - index++ - } - } - - return placeholders -} - -func (s *Schema) ImmutablePlaceholders() []string { - placeholders := []string{} - index := 1 - for i := range s.Fields { - if s.Fields[i].IsImmutable() { - placeholders = append(placeholders, fmt.Sprintf("$%d", index)) - index++ - } - } - - return placeholders -} - -func (s *Schema) IgnoredFields() []Field { - fields := []Field{} - for i := range s.Fields { - if s.Fields[i].Tag.Ignore { - fields = append(fields, s.Fields[i]) - } - } - - return fields -} - -func (s Schema) AtttributeFields() []Field { - fields := []Field{} - for i := range s.Fields { - f := s.Fields[i] - if !f.Tag.Ignore && !s.Fields[i].IsAssociation() && !f.Tag.PrimaryKey { - fields = append(fields, s.Fields[i]) - } - } - - return fields -} - -func (s Schema) AttributeFieldNames() []string { - fields := []string{} - for i := range s.Fields { - f := s.Fields[i] - if !f.Tag.Ignore && !s.Fields[i].IsAssociation() && !f.Tag.PrimaryKey { - fields = append(fields, s.Fields[i].Name) - } - } - - return fields -} - -func (s Schema) FieldNames() []string { - fields := []string{} - for i := range s.Fields { - fields = append(fields, s.Fields[i].Name) - } - - return fields -} - -func (s Schema) ColumnFields() []Field { - fields := []Field{} - for i := range s.Fields { - f := s.Fields[i] - if !f.Tag.Ignore && !s.Fields[i].IsAssociation() { - fields = append(fields, s.Fields[i]) - } - } - - return fields -} - -func (s Schema) ColumnFieldNames() []string { - fields := []string{} - for i := range s.Fields { - f := s.Fields[i] - if !f.Tag.Ignore && !s.Fields[i].IsAssociation() { - fields = append(fields, s.Fields[i].Name) - } - } - - return fields -} - -func (s Schema) Columns() []string { - fields := []string{} - for _, f := range s.ColumnFields() { - fields = append(fields, f.ColumnName()) - } - - return fields -} - -func (s *Schema) MutableFields() []Field { - fields := []Field{} - for i := range s.Fields { - if s.Fields[i].IsMutable() { - fields = append(fields, s.Fields[i]) - } - } - - return fields -} - -func (s *Schema) MutableFieldKeys() []string { - fields := []string{} - for i := range s.Fields { - if s.Fields[i].IsMutable() { - fields = append(fields, gooostrings.ToSnakeCase(s.Fields[i].Name)) - } - } - - return fields -} - -func (s Schema) AssociationFields() []Field { - fields := []Field{} - for i := range s.Fields { - if s.Fields[i].IsAssociation() { - fields = append(fields, s.Fields[i]) - } - } - - return fields -} - -func (s Schema) AssociationFieldIdents() []renderer.AssociationIdent { - idents := []renderer.AssociationIdent{} - for i := range s.Fields { - if s.Fields[i].IsAssociation() { - field := s.Fields[i] - t := fmt.Stringer(field.Type) - ok := valuetype.MaySlice(t) - if v, ok := t.(valuetype.Elementer); ok { - t = v.Element() - } - - typeName := gooostrings.ToSnakeCase(t.String()) - primaryKey := field.AssociationPrimaryKey() - idents = append(idents, renderer.AssociationIdent{ - PrimaryKey: primaryKey, - FieldName: field.Name, - TypeName: typeName, - TypeElementExpr: field.TypeElementExpr, - Slice: ok, - Ref: field.IsRef(), - }) - } - } - - return idents -} - -func (s Schema) PrimaryKey() string { - for i := range s.Fields { - if s.Fields[i].Tag.PrimaryKey { - return s.Fields[i].Name - } - } - - return "" -} diff --git a/pkg/core/schemav2/template/components/entry.go.tmpl b/pkg/core/schema/template/components/entry.go.tmpl similarity index 100% rename from pkg/core/schemav2/template/components/entry.go.tmpl rename to pkg/core/schema/template/components/entry.go.tmpl diff --git a/pkg/core/schemav2/template/components/file.go.tmpl b/pkg/core/schema/template/components/file.go.tmpl similarity index 100% rename from pkg/core/schemav2/template/components/file.go.tmpl rename to pkg/core/schema/template/components/file.go.tmpl diff --git a/pkg/core/schemav2/template/components/route.go.tmpl b/pkg/core/schema/template/components/route.go.tmpl similarity index 100% rename from pkg/core/schemav2/template/components/route.go.tmpl rename to pkg/core/schema/template/components/route.go.tmpl diff --git a/pkg/core/schemav2/template/components/struct.go.tmpl b/pkg/core/schema/template/components/struct.go.tmpl similarity index 100% rename from pkg/core/schemav2/template/components/struct.go.tmpl rename to pkg/core/schema/template/components/struct.go.tmpl diff --git a/pkg/core/schemav2/template/file.go b/pkg/core/schema/template/file.go similarity index 100% rename from pkg/core/schemav2/template/file.go rename to pkg/core/schema/template/file.go diff --git a/pkg/core/schemav2/template/format.go b/pkg/core/schema/template/format.go similarity index 100% rename from pkg/core/schemav2/template/format.go rename to pkg/core/schema/template/format.go diff --git a/pkg/core/schemav2/template/main.go b/pkg/core/schema/template/main.go similarity index 93% rename from pkg/core/schemav2/template/main.go rename to pkg/core/schema/template/main.go index 6e174fd..acb42fe 100644 --- a/pkg/core/schemav2/template/main.go +++ b/pkg/core/schema/template/main.go @@ -5,7 +5,7 @@ import ( "embed" "text/template" - "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi" "github.com/version-1/gooo/pkg/toolkit/errors" ) diff --git a/pkg/core/schemav2/template/namespace.go b/pkg/core/schema/template/namespace.go similarity index 100% rename from pkg/core/schemav2/template/namespace.go rename to pkg/core/schema/template/namespace.go diff --git a/pkg/core/schemav2/template/partial/partial.go b/pkg/core/schema/template/partial/partial.go similarity index 100% rename from pkg/core/schemav2/template/partial/partial.go rename to pkg/core/schema/template/partial/partial.go diff --git a/pkg/core/schemav2/template/route.go b/pkg/core/schema/template/route.go similarity index 97% rename from pkg/core/schemav2/template/route.go rename to pkg/core/schema/template/route.go index f11423d..869da77 100644 --- a/pkg/core/schemav2/template/route.go +++ b/pkg/core/schema/template/route.go @@ -6,7 +6,7 @@ import ( "strings" "text/template" - "github.com/version-1/gooo/pkg/core/schemav2/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi" "github.com/version-1/gooo/pkg/toolkit/errors" ) diff --git a/pkg/core/schemav2/template/schema.go b/pkg/core/schema/template/schema.go similarity index 96% rename from pkg/core/schemav2/template/schema.go rename to pkg/core/schema/template/schema.go index 3b7980c..7544c20 100644 --- a/pkg/core/schemav2/template/schema.go +++ b/pkg/core/schema/template/schema.go @@ -6,8 +6,8 @@ import ( "strings" "text/template" - "github.com/version-1/gooo/pkg/core/schemav2/openapi" - "github.com/version-1/gooo/pkg/core/schemav2/template/partial" + "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/template/partial" "github.com/version-1/gooo/pkg/toolkit/errors" ) From 709b2ea17630c5123a13f7174030a97b85bec0e4 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 07:57:35 -0800 Subject: [PATCH 11/20] make api & datasource dir --- examples/bare/cmd/app.go | 8 ++++---- examples/core/generated/internal/schema/schema.go | 6 +++--- examples/core/generated/main.go | 14 +++++++------- pkg/command/migration/adapter/yaml/schema.go | 2 +- pkg/command/migration/adapter/yaml/yaml.go | 2 +- pkg/command/migration/migration.go | 2 +- pkg/command/migration/reader/reader.go | 2 +- pkg/command/migration/reader/record.go | 2 +- pkg/command/migration/runner/runner.go | 2 +- pkg/command/migration/runner/yaml.go | 2 +- pkg/core/{ => api}/app/app.go | 2 +- pkg/core/{ => api}/app/config.go | 0 pkg/core/{ => api}/app/helper.go | 6 +++--- pkg/core/{ => api}/context/context.go | 0 pkg/core/{ => api}/middleware/middleware.go | 0 pkg/core/{ => api}/middleware/middleware_test.go | 0 pkg/core/{ => api}/request/query.go | 0 pkg/core/{ => api}/request/request.go | 2 +- pkg/core/{ => api}/response/adapter.go | 0 pkg/core/{ => api}/response/factory.go | 0 pkg/core/{ => api}/response/response.go | 0 pkg/core/{datasource => api/route}/.keep | 0 pkg/core/{ => api}/route/factory.go | 2 +- pkg/core/{ => api}/route/group.go | 0 pkg/core/{ => api}/route/handler.go | 4 ++-- pkg/core/{ => api}/route/params.go | 0 pkg/{core/route => datasource}/.keep | 0 pkg/{core => datasource}/db/db.go | 2 +- pkg/{core => datasource}/db/logger.go | 2 +- pkg/{core => }/datasource/logging/logging.go | 0 pkg/{core => }/datasource/orm/errors/errors.go | 0 pkg/{core => }/datasource/orm/executor.go | 0 pkg/{core => }/datasource/orm/orm.go | 0 pkg/{core => }/datasource/orm/orm_test.go | 0 .../datasource/orm/validator/validator.go | 0 pkg/{core => }/datasource/query/query.go | 0 pkg/toolkit/middleware/middleware.go | 2 +- 37 files changed, 32 insertions(+), 32 deletions(-) rename pkg/core/{ => api}/app/app.go (96%) rename pkg/core/{ => api}/app/config.go (100%) rename pkg/core/{ => api}/app/helper.go (83%) rename pkg/core/{ => api}/context/context.go (100%) rename pkg/core/{ => api}/middleware/middleware.go (100%) rename pkg/core/{ => api}/middleware/middleware_test.go (100%) rename pkg/core/{ => api}/request/query.go (100%) rename pkg/core/{ => api}/request/request.go (96%) rename pkg/core/{ => api}/response/adapter.go (100%) rename pkg/core/{ => api}/response/factory.go (100%) rename pkg/core/{ => api}/response/response.go (100%) rename pkg/core/{datasource => api/route}/.keep (100%) rename pkg/core/{ => api}/route/factory.go (97%) rename pkg/core/{ => api}/route/group.go (100%) rename pkg/core/{ => api}/route/handler.go (94%) rename pkg/core/{ => api}/route/params.go (100%) rename pkg/{core/route => datasource}/.keep (100%) rename pkg/{core => datasource}/db/db.go (98%) rename pkg/{core => datasource}/db/logger.go (97%) rename pkg/{core => }/datasource/logging/logging.go (100%) rename pkg/{core => }/datasource/orm/errors/errors.go (100%) rename pkg/{core => }/datasource/orm/executor.go (100%) rename pkg/{core => }/datasource/orm/orm.go (100%) rename pkg/{core => }/datasource/orm/orm_test.go (100%) rename pkg/{core => }/datasource/orm/validator/validator.go (100%) rename pkg/{core => }/datasource/query/query.go (100%) diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index 2efd525..f5b6229 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -7,10 +7,10 @@ import ( "time" "github.com/version-1/gooo/examples/bare/internal/swagger" - "github.com/version-1/gooo/pkg/core/app" - "github.com/version-1/gooo/pkg/core/request" - "github.com/version-1/gooo/pkg/core/response" - "github.com/version-1/gooo/pkg/core/route" + "github.com/version-1/gooo/pkg/core/api/app" + "github.com/version-1/gooo/pkg/core/api/request" + "github.com/version-1/gooo/pkg/core/api/response" + "github.com/version-1/gooo/pkg/core/api/route" "github.com/version-1/gooo/pkg/toolkit/logger" "github.com/version-1/gooo/pkg/toolkit/middleware" ) diff --git a/examples/core/generated/internal/schema/schema.go b/examples/core/generated/internal/schema/schema.go index 6fa8656..72a47d5 100644 --- a/examples/core/generated/internal/schema/schema.go +++ b/examples/core/generated/internal/schema/schema.go @@ -8,12 +8,12 @@ type Error struct { } type User struct { - ID int Username string Obj struct { Hoge string Fuga string } + ID int } type MutateUser struct { @@ -21,13 +21,13 @@ type MutateUser struct { } type Post struct { - ID int UserId int Title string Content string + ID int } type MutatePost struct { - Content string Title string + Content string } diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 44030c8..4dc7274 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -7,10 +7,10 @@ import ( "net/http" "github.com/version-1/gooo/examples/core/internal/schema" - "github.com/version-1/gooo/pkg/core/app" - "github.com/version-1/gooo/pkg/core/request" - "github.com/version-1/gooo/pkg/core/response" - "github.com/version-1/gooo/pkg/core/route" + "github.com/version-1/gooo/pkg/core/api/app" + "github.com/version-1/gooo/pkg/core/api/request" + "github.com/version-1/gooo/pkg/core/api/response" + "github.com/version-1/gooo/pkg/core/api/route" "github.com/version-1/gooo/pkg/toolkit/logger" ) @@ -45,13 +45,13 @@ func RegisterRoutes(srv *app.App) { route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { // do something }), - route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { // do something }), - route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { // do something }), - route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { // do something }), route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { diff --git a/pkg/command/migration/adapter/yaml/schema.go b/pkg/command/migration/adapter/yaml/schema.go index 3da6878..9fb33a5 100644 --- a/pkg/command/migration/adapter/yaml/schema.go +++ b/pkg/command/migration/adapter/yaml/schema.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" "github.com/version-1/gooo/pkg/toolkit/errors" yaml "gopkg.in/yaml.v3" ) diff --git a/pkg/command/migration/adapter/yaml/yaml.go b/pkg/command/migration/adapter/yaml/yaml.go index b5702be..4ae7b5d 100644 --- a/pkg/command/migration/adapter/yaml/yaml.go +++ b/pkg/command/migration/adapter/yaml/yaml.go @@ -6,7 +6,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/helper" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" ) type YamlElement interface { diff --git a/pkg/command/migration/migration.go b/pkg/command/migration/migration.go index 440e6a6..c1e6b4c 100644 --- a/pkg/command/migration/migration.go +++ b/pkg/command/migration/migration.go @@ -12,7 +12,7 @@ import ( _ "github.com/lib/pq" "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/runner" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/command/migration/reader/reader.go b/pkg/command/migration/reader/reader.go index 581d41c..41f7e8b 100644 --- a/pkg/command/migration/reader/reader.go +++ b/pkg/command/migration/reader/reader.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" yaml "gopkg.in/yaml.v3" ) diff --git a/pkg/command/migration/reader/record.go b/pkg/command/migration/reader/record.go index d3c5369..ef3bedc 100644 --- a/pkg/command/migration/reader/record.go +++ b/pkg/command/migration/reader/record.go @@ -7,7 +7,7 @@ import ( "time" "github.com/version-1/gooo/pkg/command/migration/constants" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" ) type Record struct { diff --git a/pkg/command/migration/runner/runner.go b/pkg/command/migration/runner/runner.go index b14075f..cf8296a 100644 --- a/pkg/command/migration/runner/runner.go +++ b/pkg/command/migration/runner/runner.go @@ -6,7 +6,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/reader" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/command/migration/runner/yaml.go b/pkg/command/migration/runner/yaml.go index 00d272a..7df3c98 100644 --- a/pkg/command/migration/runner/yaml.go +++ b/pkg/command/migration/runner/yaml.go @@ -7,7 +7,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/version-1/gooo/pkg/command/migration/adapter/yaml" - "github.com/version-1/gooo/pkg/core/db" + "github.com/version-1/gooo/pkg/datasource/db" ) type Yaml struct { diff --git a/pkg/core/app/app.go b/pkg/core/api/app/app.go similarity index 96% rename from pkg/core/app/app.go rename to pkg/core/api/app/app.go index b63b3b4..cf6bbc2 100644 --- a/pkg/core/app/app.go +++ b/pkg/core/api/app/app.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - "github.com/version-1/gooo/pkg/core/middleware" + "github.com/version-1/gooo/pkg/core/api/middleware" "github.com/version-1/gooo/pkg/toolkit/errors" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/core/app/config.go b/pkg/core/api/app/config.go similarity index 100% rename from pkg/core/app/config.go rename to pkg/core/api/app/config.go diff --git a/pkg/core/app/helper.go b/pkg/core/api/app/helper.go similarity index 83% rename from pkg/core/app/helper.go rename to pkg/core/api/app/helper.go index 489a353..6bdf708 100644 --- a/pkg/core/app/helper.go +++ b/pkg/core/api/app/helper.go @@ -3,9 +3,9 @@ package app import ( "net/http" - "github.com/version-1/gooo/pkg/core/context" - "github.com/version-1/gooo/pkg/core/middleware" - "github.com/version-1/gooo/pkg/core/route" + "github.com/version-1/gooo/pkg/core/api/context" + "github.com/version-1/gooo/pkg/core/api/middleware" + "github.com/version-1/gooo/pkg/core/api/route" helper "github.com/version-1/gooo/pkg/toolkit/middleware" ) diff --git a/pkg/core/context/context.go b/pkg/core/api/context/context.go similarity index 100% rename from pkg/core/context/context.go rename to pkg/core/api/context/context.go diff --git a/pkg/core/middleware/middleware.go b/pkg/core/api/middleware/middleware.go similarity index 100% rename from pkg/core/middleware/middleware.go rename to pkg/core/api/middleware/middleware.go diff --git a/pkg/core/middleware/middleware_test.go b/pkg/core/api/middleware/middleware_test.go similarity index 100% rename from pkg/core/middleware/middleware_test.go rename to pkg/core/api/middleware/middleware_test.go diff --git a/pkg/core/request/query.go b/pkg/core/api/request/query.go similarity index 100% rename from pkg/core/request/query.go rename to pkg/core/api/request/query.go diff --git a/pkg/core/request/request.go b/pkg/core/api/request/request.go similarity index 96% rename from pkg/core/request/request.go rename to pkg/core/api/request/request.go index fd7b018..f03dc1c 100644 --- a/pkg/core/request/request.go +++ b/pkg/core/api/request/request.go @@ -6,7 +6,7 @@ import ( "io" "net/http" - "github.com/version-1/gooo/pkg/core/context" + "github.com/version-1/gooo/pkg/core/api/context" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/core/response/adapter.go b/pkg/core/api/response/adapter.go similarity index 100% rename from pkg/core/response/adapter.go rename to pkg/core/api/response/adapter.go diff --git a/pkg/core/response/factory.go b/pkg/core/api/response/factory.go similarity index 100% rename from pkg/core/response/factory.go rename to pkg/core/api/response/factory.go diff --git a/pkg/core/response/response.go b/pkg/core/api/response/response.go similarity index 100% rename from pkg/core/response/response.go rename to pkg/core/api/response/response.go diff --git a/pkg/core/datasource/.keep b/pkg/core/api/route/.keep similarity index 100% rename from pkg/core/datasource/.keep rename to pkg/core/api/route/.keep diff --git a/pkg/core/route/factory.go b/pkg/core/api/route/factory.go similarity index 97% rename from pkg/core/route/factory.go rename to pkg/core/api/route/factory.go index 1ef9436..b9c142f 100644 --- a/pkg/core/route/factory.go +++ b/pkg/core/api/route/factory.go @@ -3,7 +3,7 @@ package route import ( "net/http" - "github.com/version-1/gooo/pkg/core/response" + "github.com/version-1/gooo/pkg/core/api/response" ) func JSON[I, O any]() *Handler[I, O] { diff --git a/pkg/core/route/group.go b/pkg/core/api/route/group.go similarity index 100% rename from pkg/core/route/group.go rename to pkg/core/api/route/group.go diff --git a/pkg/core/route/handler.go b/pkg/core/api/route/handler.go similarity index 94% rename from pkg/core/route/handler.go rename to pkg/core/api/route/handler.go index b1be1db..7b87067 100644 --- a/pkg/core/route/handler.go +++ b/pkg/core/api/route/handler.go @@ -7,8 +7,8 @@ import ( "path/filepath" "strings" - "github.com/version-1/gooo/pkg/core/request" - "github.com/version-1/gooo/pkg/core/response" + "github.com/version-1/gooo/pkg/core/api/request" + "github.com/version-1/gooo/pkg/core/api/response" "github.com/version-1/gooo/pkg/toolkit/middleware" ) diff --git a/pkg/core/route/params.go b/pkg/core/api/route/params.go similarity index 100% rename from pkg/core/route/params.go rename to pkg/core/api/route/params.go diff --git a/pkg/core/route/.keep b/pkg/datasource/.keep similarity index 100% rename from pkg/core/route/.keep rename to pkg/datasource/.keep diff --git a/pkg/core/db/db.go b/pkg/datasource/db/db.go similarity index 98% rename from pkg/core/db/db.go rename to pkg/datasource/db/db.go index 3ceb4a5..2346a1d 100644 --- a/pkg/core/db/db.go +++ b/pkg/datasource/db/db.go @@ -7,7 +7,7 @@ import ( "github.com/google/uuid" "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/logger" + "github.com/version-1/gooo/pkg/toolkit/logger" ) type QueryRunner interface { diff --git a/pkg/core/db/logger.go b/pkg/datasource/db/logger.go similarity index 97% rename from pkg/core/db/logger.go rename to pkg/datasource/db/logger.go index e38aee6..35d6bfb 100644 --- a/pkg/core/db/logger.go +++ b/pkg/datasource/db/logger.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/version-1/gooo/pkg/logger" + "github.com/version-1/gooo/pkg/toolkit/logger" ) type QueryLogger interface { diff --git a/pkg/core/datasource/logging/logging.go b/pkg/datasource/logging/logging.go similarity index 100% rename from pkg/core/datasource/logging/logging.go rename to pkg/datasource/logging/logging.go diff --git a/pkg/core/datasource/orm/errors/errors.go b/pkg/datasource/orm/errors/errors.go similarity index 100% rename from pkg/core/datasource/orm/errors/errors.go rename to pkg/datasource/orm/errors/errors.go diff --git a/pkg/core/datasource/orm/executor.go b/pkg/datasource/orm/executor.go similarity index 100% rename from pkg/core/datasource/orm/executor.go rename to pkg/datasource/orm/executor.go diff --git a/pkg/core/datasource/orm/orm.go b/pkg/datasource/orm/orm.go similarity index 100% rename from pkg/core/datasource/orm/orm.go rename to pkg/datasource/orm/orm.go diff --git a/pkg/core/datasource/orm/orm_test.go b/pkg/datasource/orm/orm_test.go similarity index 100% rename from pkg/core/datasource/orm/orm_test.go rename to pkg/datasource/orm/orm_test.go diff --git a/pkg/core/datasource/orm/validator/validator.go b/pkg/datasource/orm/validator/validator.go similarity index 100% rename from pkg/core/datasource/orm/validator/validator.go rename to pkg/datasource/orm/validator/validator.go diff --git a/pkg/core/datasource/query/query.go b/pkg/datasource/query/query.go similarity index 100% rename from pkg/core/datasource/query/query.go rename to pkg/datasource/query/query.go diff --git a/pkg/toolkit/middleware/middleware.go b/pkg/toolkit/middleware/middleware.go index d8a6788..8bfec18 100644 --- a/pkg/toolkit/middleware/middleware.go +++ b/pkg/toolkit/middleware/middleware.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - "github.com/version-1/gooo/pkg/core/middleware" + "github.com/version-1/gooo/pkg/core/api/middleware" "github.com/version-1/gooo/pkg/toolkit/logger" ) From 82d535b9c517e16a14aeda0268d5b3e9b008ad99 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 11:58:13 -0800 Subject: [PATCH 12/20] Add ordedMap for persist order --- examples/core/cmd/app.go | 4 +- .../core/generated/internal/schema/schema.go | 6 +- examples/core/generated/main.go | 14 +-- pkg/core/schema/generate.go | 6 +- pkg/core/schema/openapi/schema.go | 105 +----------------- pkg/core/schema/openapi/v3_0_0/schema.go | 104 +++++++++++++++++ pkg/core/schema/openapi/yaml/yaml.go | 66 +++++++++++ pkg/core/schema/openapi/yaml/yaml_test.go | 8 ++ pkg/core/schema/template/format.go | 3 + pkg/core/schema/template/main.go | 4 +- pkg/core/schema/template/route.go | 22 ++-- pkg/core/schema/template/schema.go | 31 ++++-- 12 files changed, 233 insertions(+), 140 deletions(-) create mode 100644 pkg/core/schema/openapi/v3_0_0/schema.go create mode 100644 pkg/core/schema/openapi/yaml/yaml.go create mode 100644 pkg/core/schema/openapi/yaml/yaml_test.go diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index 9db04d8..ae1c50c 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -4,11 +4,11 @@ import ( "fmt" schema "github.com/version-1/gooo/pkg/core/schema" - "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" ) func main() { - s, err := openapi.New("./examples/bare/internal/swagger/swagger.yml") + s, err := v3_0_0.New("./examples/bare/internal/swagger/swagger.yml") if err != nil { panic(err) } diff --git a/examples/core/generated/internal/schema/schema.go b/examples/core/generated/internal/schema/schema.go index 72a47d5..e62aa4d 100644 --- a/examples/core/generated/internal/schema/schema.go +++ b/examples/core/generated/internal/schema/schema.go @@ -8,12 +8,12 @@ type Error struct { } type User struct { + ID int Username string Obj struct { Hoge string Fuga string } - ID int } type MutateUser struct { @@ -21,10 +21,10 @@ type MutateUser struct { } type Post struct { - UserId int + ID int + userID int Title string Content string - ID int } type MutatePost struct { diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 4dc7274..756a2ae 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -39,10 +39,13 @@ func RegisterRoutes(srv *app.App) { routes := route.GroupHandler{ Path: "/users", Handlers: []route.HandlerInterface{ + route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something + }), route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { // do something }), - route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { // do something }), route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { @@ -51,24 +54,21 @@ func RegisterRoutes(srv *app.App) { route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { // do something }), - route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { // do something }), route.JSON[schema.MutatePost, schema.Post]().Post("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { // do something }), - route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), route.JSON[schema.MutatePost, schema.Post]().Patch("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { // do something }), route.JSON[request.Void, schema.Post]().Delete("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { // do something }), + route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), }, } app.WithDefaultMiddlewares(srv, routes.Children()...) diff --git a/pkg/core/schema/generate.go b/pkg/core/schema/generate.go index 7c24066..811f2d4 100644 --- a/pkg/core/schema/generate.go +++ b/pkg/core/schema/generate.go @@ -5,18 +5,18 @@ import ( "path/filepath" "github.com/version-1/gooo/pkg/core/generator" - "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/version-1/gooo/pkg/core/schema/template" ) type Generator struct { - r *openapi.RootSchema + r *v3_0_0.RootSchema outputs []generator.Template baseURL string OutDir string } -func NewGenerator(r *openapi.RootSchema, outDir string, baseURL string) *Generator { +func NewGenerator(r *v3_0_0.RootSchema, outDir string, baseURL string) *Generator { return &Generator{r: r, OutDir: outDir, baseURL: baseURL} } diff --git a/pkg/core/schema/openapi/schema.go b/pkg/core/schema/openapi/schema.go index 363161b..9a9a3ed 100644 --- a/pkg/core/schema/openapi/schema.go +++ b/pkg/core/schema/openapi/schema.go @@ -1,106 +1,5 @@ package openapi -import ( - "os" +import "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" - "gopkg.in/yaml.v3" -) - -func New(path string) (*RootSchema, error) { - bytes, err := os.ReadFile(path) - if err != nil { - return nil, err - } - s := &RootSchema{} - if err := yaml.Unmarshal(bytes, &s); err != nil { - return s, err - } - - return s, nil -} - -type RequestBody struct { - Description string `json:"description"` - Content map[string]MediaType `json:"content"` -} - -type Responses map[string]Response - -type Response struct { - Description string `json:"description"` - Content map[string]MediaType `json:"content"` -} - -type MediaType struct { - Schema Schema `json:"schema"` -} - -type Content struct { - Schema RootSchema `json:"schema"` -} - -type Parameter struct { - Name string `json:"name"` - In string `json:"in"` - Description string `json:"description"` - Required bool `json:"required"` - Schema RootSchema `json:"schema"` -} - -type Operation struct { - Summary string `json:"summary"` - Description string `json:"description"` - OperationId string `json:"operationId"` - Parameters []Parameter `json:"parameters"` - RequestBody RequestBody `json:"requestBody" yaml:"requestBody"` - Responses Responses `json:"responses"` -} - -type PathItem struct { - Get *Operation `json:"get"` - Post *Operation `json:"post"` - Put *Operation `json:"put"` - Patch *Operation `json:"patch"` - Delete *Operation `json:"delete"` -} - -type Info struct { - Title string `json:"title"` - Description string `json:"description"` - Version string `json:"version"` -} - -type Server struct { - Url string `json:"url"` - Description string `json:"description"` -} - -type Components struct { - Schemas map[string]Schema `json:"schemas"` -} - -type Schema struct { - Type string `json:"type"` - Properties map[string]Property `json:"properties"` - Ref string `json:"$ref" yaml:"$ref"` - Items Property `json:"items"` -} - -type Property struct { - Ref string `json:"$ref" yaml:"$ref"` - Type string `json:"type"` - Properties map[string]Property `json:"properties"` - Items *Property `json:"items"` - Format string `json:"format"` - Sample string `json:"sample"` - Required bool `json:"required"` -} - -// version. 3.0.x -type RootSchema struct { - OpenAPI string `json:"openapi"` - Info Info `json:"info"` - Paths map[string]PathItem `json:"paths"` - Servers []Server `json:"servers"` - Components Components `json:"components"` -} +type RootSchema v3_0_0.RootSchema diff --git a/pkg/core/schema/openapi/v3_0_0/schema.go b/pkg/core/schema/openapi/v3_0_0/schema.go new file mode 100644 index 0000000..6dc4ae1 --- /dev/null +++ b/pkg/core/schema/openapi/v3_0_0/schema.go @@ -0,0 +1,104 @@ +package v3_0_0 + +import ( + "os" + + "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" +) + +func New(path string) (*RootSchema, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, err + } + s := &RootSchema{} + if err := yaml.Unmarshal(bytes, &s); err != nil { + return s, err + } + + return s, nil +} + +type RequestBody struct { + Description string `json:"description"` + Content yaml.OrderedMap[MediaType] `json:"content"` +} + +type Response struct { + Description string `json:"description"` + Content yaml.OrderedMap[MediaType] `json:"content"` +} + +type MediaType struct { + Schema Schema `json:"schema"` +} + +type Content struct { + Schema RootSchema `json:"schema"` +} + +type Parameter struct { + Name string `json:"name"` + In string `json:"in"` + Description string `json:"description"` + Required bool `json:"required"` + Schema RootSchema `json:"schema"` +} + +type Operation struct { + Summary string `json:"summary"` + Description string `json:"description"` + OperationId string `json:"operationId"` + Parameters []Parameter `json:"parameters"` + RequestBody RequestBody `json:"requestBody" yaml:"requestBody"` + Responses yaml.OrderedMap[Response] `json:"responses"` +} + +type PathItem struct { + Get *Operation `json:"get"` + Post *Operation `json:"post"` + Put *Operation `json:"put"` + Patch *Operation `json:"patch"` + Delete *Operation `json:"delete"` +} + +type Info struct { + Title string `json:"title"` + Description string `json:"description"` + Version string `json:"version"` +} + +type Server struct { + Url string `json:"url"` + Description string `json:"description"` +} + +type Components struct { + Schemas yaml.OrderedMap[Schema] `json:"schemas"` +} + +type Schema struct { + Type string `json:"type"` + Properties yaml.OrderedMap[Property] `json:"properties"` + Ref string `json:"$ref" yaml:"$ref"` + Items Property `json:"items"` +} + +type Property struct { + Ref string `json:"$ref" yaml:"$ref"` + Type string `json:"type"` + Properties yaml.OrderedMap[Property] `json:"properties"` + Items *Property `json:"items"` + Format string `json:"format"` + Sample string `json:"sample"` + Required bool `json:"required"` +} + +// version. 3.0.x +type RootSchema struct { + OpenAPI string `json:"openapi"` + Info Info `json:"info"` + Paths yaml.OrderedMap[PathItem] `json:"paths"` + Servers []Server `json:"servers"` + Components Components `json:"components"` +} diff --git a/pkg/core/schema/openapi/yaml/yaml.go b/pkg/core/schema/openapi/yaml/yaml.go new file mode 100644 index 0000000..b03e52d --- /dev/null +++ b/pkg/core/schema/openapi/yaml/yaml.go @@ -0,0 +1,66 @@ +package yaml + +import ( + yaml "gopkg.in/yaml.v3" +) + +func Unmarshal(b []byte, d any) error { + return yaml.Unmarshal(b, d) +} + +type OrderedMap[T any] struct { + keys []string + Values map[string]T +} + +func (o *OrderedMap[T]) Set(key string, value T) { + o.keys = append(o.keys, key) + o.Values[key] = value +} + +func (o OrderedMap[T]) Get(key string) T { + return o.Values[key] +} + +func (o OrderedMap[T]) Each(cb func(key string, v T) error) error { + for _, key := range o.keys { + err := cb(key, o.Values[key]) + if err != nil { + return err + } + } + + return nil +} + +func (o OrderedMap[T]) Index(i int) (string, T) { + key := o.keys[i] + return key, o.Values[key] +} + +func (o OrderedMap[T]) Len() int { + return len(o.keys) +} + +func (o OrderedMap[T]) Keys() []string { + return o.keys +} + +func (o *OrderedMap[T]) UnmarshalYAML(node *yaml.Node) error { + if node.Kind != yaml.MappingNode { + return nil + } + + o.Values = make(map[string]T) + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i].Value + value := node.Content[i+1] + var v T + if err := value.Decode(&v); err != nil { + return err + } + o.Set(key, v) + } + + return nil +} diff --git a/pkg/core/schema/openapi/yaml/yaml_test.go b/pkg/core/schema/openapi/yaml/yaml_test.go new file mode 100644 index 0000000..abc7a78 --- /dev/null +++ b/pkg/core/schema/openapi/yaml/yaml_test.go @@ -0,0 +1,8 @@ +package yaml + +import ( + "testing" +) + +func TestYaml_Load(t *testing.T) { +} diff --git a/pkg/core/schema/template/format.go b/pkg/core/schema/template/format.go index 0cbf44f..19827e3 100644 --- a/pkg/core/schema/template/format.go +++ b/pkg/core/schema/template/format.go @@ -1,6 +1,7 @@ package template import ( + "fmt" "go/format" "golang.org/x/tools/imports" @@ -9,11 +10,13 @@ import ( func pretify(filename, s string) ([]byte, error) { formatted, err := format.Source([]byte(s)) if err != nil { + fmt.Println("Error processing format", s) return []byte{}, err } processed, err := imports.Process(filename, formatted, nil) if err != nil { + fmt.Println("Error processing imports", s) return formatted, err } diff --git a/pkg/core/schema/template/main.go b/pkg/core/schema/template/main.go index acb42fe..78f4b2c 100644 --- a/pkg/core/schema/template/main.go +++ b/pkg/core/schema/template/main.go @@ -5,7 +5,7 @@ import ( "embed" "text/template" - "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/version-1/gooo/pkg/toolkit/errors" ) @@ -13,7 +13,7 @@ import ( var tmpl embed.FS type Main struct { - Schema *openapi.RootSchema + Schema *v3_0_0.RootSchema Dependencies []string Routes string } diff --git a/pkg/core/schema/template/route.go b/pkg/core/schema/template/route.go index 869da77..54ee7d3 100644 --- a/pkg/core/schema/template/route.go +++ b/pkg/core/schema/template/route.go @@ -6,7 +6,8 @@ import ( "strings" "text/template" - "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" + "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" "github.com/version-1/gooo/pkg/toolkit/errors" ) @@ -28,10 +29,10 @@ func renderRoutes(routes []Route) (string, error) { return b.String(), nil } -func extractRoutes(r *openapi.RootSchema) []Route { +func extractRoutes(r *v3_0_0.RootSchema) []Route { routes := []Route{} - for path, pathItem := range r.Paths { - m := map[string]*openapi.Operation{ + r.Paths.Each(func(path string, pathItem v3_0_0.PathItem) error { + m := map[string]*v3_0_0.Operation{ "Get": pathItem.Get, "Post": pathItem.Post, "Patch": pathItem.Patch, @@ -66,13 +67,15 @@ func extractRoutes(r *openapi.RootSchema) []Route { routes = append(routes, route) } } - } + + return nil + }) return routes } -func detectInputType(op *openapi.Operation, contentType string) string { - schema := op.RequestBody.Content[contentType].Schema +func detectInputType(op *v3_0_0.Operation, contentType string) string { + schema := op.RequestBody.Content.Get(contentType).Schema ref := "" if schema.Ref != "" { ref = schema.Ref @@ -86,8 +89,9 @@ func detectInputType(op *openapi.Operation, contentType string) string { return schemaName } -func detectOutputType(op *openapi.Operation, statusCode int, contentType string) string { - schema := op.Responses[strconv.Itoa(statusCode)].Content[contentType].Schema +func detectOutputType(op *v3_0_0.Operation, statusCode int, contentType string) string { + responses := yaml.OrderedMap[v3_0_0.Response](op.Responses) + schema := responses.Get(strconv.Itoa(statusCode)).Content.Get(contentType).Schema ref := "" if schema.Ref != "" { ref = schema.Ref diff --git a/pkg/core/schema/template/schema.go b/pkg/core/schema/template/schema.go index 7544c20..95933ef 100644 --- a/pkg/core/schema/template/schema.go +++ b/pkg/core/schema/template/schema.go @@ -6,13 +6,14 @@ import ( "strings" "text/template" - "github.com/version-1/gooo/pkg/core/schema/openapi" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" + "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" "github.com/version-1/gooo/pkg/core/schema/template/partial" "github.com/version-1/gooo/pkg/toolkit/errors" ) type SchemaFile struct { - Schema *openapi.RootSchema + Schema *v3_0_0.RootSchema PackageName string Content string } @@ -21,18 +22,22 @@ func (s SchemaFile) Filename() string { return "internal/schema/schema" } -// FIXME: yaml.v3 doesnt guarantee the order of the fields and schemas func (s SchemaFile) Render() (string, error) { schemas := []Schema{} - for name, schema := range s.Schema.Components.Schemas { - fields, err := extractFields(schema.Properties, "") + err := s.Schema.Components.Schemas.Each(func(key string, s v3_0_0.Schema) error { + fields, err := extractFields(s.Properties, "") if err != nil { - return "", err + return err } + schemas = append(schemas, Schema{ Fields: fields, - TypeName: name, + TypeName: key, }) + return nil + }) + if err != nil { + return "", err } content, err := renderSchemas(schemas) @@ -53,7 +58,6 @@ func (s SchemaFile) Render() (string, error) { res, err := pretify(s.Filename(), b.String()) if err != nil { - fmt.Println("pretify content: ", b.String()) return "", errors.Wrap(err) } return string(res), err @@ -76,9 +80,10 @@ func renderSchemas(schemas []Schema) (string, error) { return b.String(), nil } -func extractFields(props map[string]openapi.Property, prefix string) ([]string, error) { +func extractFields(props yaml.OrderedMap[v3_0_0.Property], prefix string) ([]string, error) { var fields []string - for k, v := range props { + for i := 0; i < props.Len(); i++ { + k, v := props.Index(i) key := formatKeyname(k) if v.Ref != "" { fields = append(fields, key+" "+pointer(schemaTypeName(v.Ref))) @@ -94,7 +99,7 @@ func extractFields(props map[string]openapi.Property, prefix string) ([]string, return fields, nil } -func extractFieldType(prop openapi.Property, prefix string) (string, error) { +func extractFieldType(prop v3_0_0.Property, prefix string) (string, error) { if prop.Ref != "" { return prefix + pointer(schemaTypeName(prop.Ref)), nil } @@ -129,6 +134,10 @@ func formatKeyname(key string) string { return strings.ToUpper(key) } + if strings.HasSuffix(key, "Id") { + return key[0:len(key)-2] + "ID" + } + return Capitalize(key) } From 067abd63cbdb9391b55c7218710752bf872f0172 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 12:39:19 -0800 Subject: [PATCH 13/20] Organize test --- examples/core/cmd/app.go | 2 +- examples/core/generated/main.go | 2 +- go.mod | 5 ++- go.sum | 10 ++++- pkg/core/api/middleware/middleware_test.go | 10 ++--- pkg/toolkit/auth/auth.go | 44 ++++++++++++---------- pkg/toolkit/auth/helper.go | 9 +++-- pkg/toolkit/errors/errors_test.go | 2 +- pkg/toolkit/presenter/jsonapi/jsonapi.go | 4 +- pkg/toolkit/presenter/jsonapi/stringify.go | 2 +- pkg/toolkit/testing/cleaner/adapter/pq.go | 2 +- pkg/toolkit/testing/cleaner/cleaner.go | 4 +- 12 files changed, 54 insertions(+), 42 deletions(-) diff --git a/examples/core/cmd/app.go b/examples/core/cmd/app.go index ae1c50c..32a8bc8 100644 --- a/examples/core/cmd/app.go +++ b/examples/core/cmd/app.go @@ -13,7 +13,7 @@ func main() { panic(err) } - g := schema.NewGenerator(s, "./examples/core/generated", "github.com/version-1/gooo/examples/core") + g := schema.NewGenerator(s, "./examples/core/generated", "github.com/version-1/gooo/examples/core/generated") if err := g.Generate(); err != nil { fmt.Printf("Error: %+v\n", err) diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 756a2ae..1936242 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -6,7 +6,7 @@ import ( "log" "net/http" - "github.com/version-1/gooo/examples/core/internal/schema" + "github.com/version-1/gooo/examples/core/generated/internal/schema" "github.com/version-1/gooo/pkg/core/api/app" "github.com/version-1/gooo/pkg/core/api/request" "github.com/version-1/gooo/pkg/core/api/response" diff --git a/go.mod b/go.mod index f051787..23f1e3a 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,18 @@ go 1.22.3 require ( github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 + github.com/stretchr/testify v1.10.0 golang.org/x/tools v0.23.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 626fdb1..2c17359 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= @@ -14,6 +14,12 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= diff --git a/pkg/core/api/middleware/middleware_test.go b/pkg/core/api/middleware/middleware_test.go index 7d90318..d1ee80d 100644 --- a/pkg/core/api/middleware/middleware_test.go +++ b/pkg/core/api/middleware/middleware_test.go @@ -5,8 +5,6 @@ import ( "net/http" "reflect" "testing" - - "github.com/version-1/gooo/pkg/core/request" ) func TestMiddleware(t *testing.T) { @@ -16,7 +14,7 @@ func TestMiddleware(t *testing.T) { mw.Append(Middleware{ Name: "mw1", If: Always, - Do: func(w http.ResponseWriter, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *http.Request) bool { output = append(output, "mw1") return true }, @@ -25,7 +23,7 @@ func TestMiddleware(t *testing.T) { mw.Append(Middleware{ Name: "mw2", If: Always, - Do: func(w http.ResponseWriter, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *http.Request) bool { output = append(output, "mw2") return true }, @@ -34,7 +32,7 @@ func TestMiddleware(t *testing.T) { mw.Append(Middleware{ Name: "mw3", If: Always, - Do: func(w http.ResponseWriter, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *http.Request) bool { output = append(output, "mw3") return true }, @@ -43,7 +41,7 @@ func TestMiddleware(t *testing.T) { mw.Prepend(Middleware{ Name: "mw5", If: Always, - Do: func(w http.ResponseWriter, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *http.Request) bool { output = append(output, "mw5") return true }, diff --git a/pkg/toolkit/auth/auth.go b/pkg/toolkit/auth/auth.go index a8e14c9..5647555 100644 --- a/pkg/toolkit/auth/auth.go +++ b/pkg/toolkit/auth/auth.go @@ -1,20 +1,19 @@ package auth import ( + "encoding/json" "net/http" "os" "strings" "time" jwt "github.com/golang-jwt/jwt/v5" - "github.com/version-1/gooo/pkg/controller" - "github.com/version-1/gooo/pkg/http/request" - "github.com/version-1/gooo/pkg/http/response" + "github.com/version-1/gooo/pkg/core/api/middleware" ) type JWTAuth[T any] struct { - If func(r *request.Request) bool - OnAuthorized func(r *request.Request, sub string) error + If func(r *http.Request) bool + OnAuthorized func(r *http.Request, sub string) error PrivateKey *string TokenExpiresIn time.Duration Issuer string @@ -28,7 +27,7 @@ func (a JWTAuth[T]) GetPrivateKey() string { return *a.PrivateKey } -func (a JWTAuth[T]) Sign(r *request.Request) (string, error) { +func (a JWTAuth[T]) Sign(r *http.Request) (string, error) { claims := &jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(a.TokenExpiresIn)), Issuer: a.Issuer, @@ -38,10 +37,10 @@ func (a JWTAuth[T]) Sign(r *request.Request) (string, error) { return token.SignedString(a.GetPrivateKey()) } -func (a JWTAuth[T]) Guard() controller.Middleware { - return controller.Middleware{ +func (a JWTAuth[T]) Guard() middleware.Middleware { + return middleware.Middleware{ If: a.If, - Do: func(w *response.Response, r *request.Request) bool { + Do: func(w http.ResponseWriter, r *http.Request) bool { str := r.Header.Get("Authorization") token := strings.TrimSpace(strings.ReplaceAll(str, "Bearer ", "")) t, err := jwt.ParseWithClaims(token, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) { @@ -59,12 +58,11 @@ func (a JWTAuth[T]) Guard() controller.Middleware { } if expired { - w.JSON(map[string]string{ + renderJSON(w, map[string]string{ "code": "auth:token_expired", "error": "Unauthorized", "detail": err.Error(), - }) - w.WriteHeader(http.StatusUnauthorized) + }, http.StatusUnauthorized) return false } @@ -84,13 +82,19 @@ func (a JWTAuth[T]) Guard() controller.Middleware { } } -func reportError(w *response.Response, e error) { - w.JSON( - map[string]string{ - "code": "unauthorized", - "error": "Unauthorized", - "detail": e.Error(), - }, - ) +func renderJSON(w http.ResponseWriter, payload map[string]string, status int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(payload) +} + +func reportError(w http.ResponseWriter, e error) { + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) + payload := map[string]string{ + "code": "unauthorized", + "error": "Unauthorized", + "detail": e.Error(), + } + json.NewEncoder(w).Encode(payload) } diff --git a/pkg/toolkit/auth/helper.go b/pkg/toolkit/auth/helper.go index a0ffb55..a9448a7 100644 --- a/pkg/toolkit/auth/helper.go +++ b/pkg/toolkit/auth/helper.go @@ -1,16 +1,17 @@ package auth import ( - "github.com/version-1/gooo/pkg/context" - "github.com/version-1/gooo/pkg/http/request" + "net/http" + + "github.com/version-1/gooo/pkg/core/api/context" ) -func SetContextOnAuthorized[T any](r *request.Request, sub string, fetcher func(sub string) (T, error)) error { +func SetContextOnAuthorized[T any](r *http.Request, sub string, fetcher func(sub string) (T, error)) error { u, err := fetcher(sub) if err != nil { return err } - r.WithContext(context.WithUserConfig(r.Context(), u)) + r.WithContext(context.With(r.Context(), "user", u)) return nil } diff --git a/pkg/toolkit/errors/errors_test.go b/pkg/toolkit/errors/errors_test.go index 9e057e7..cdf7081 100644 --- a/pkg/toolkit/errors/errors_test.go +++ b/pkg/toolkit/errors/errors_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - goootesting "github.com/version-1/gooo/pkg/testing" + goootesting "github.com/version-1/gooo/pkg/toolkit/testing" ) func TestErrors(t *testing.T) { diff --git a/pkg/toolkit/presenter/jsonapi/jsonapi.go b/pkg/toolkit/presenter/jsonapi/jsonapi.go index 6e6900c..d97eb27 100644 --- a/pkg/toolkit/presenter/jsonapi/jsonapi.go +++ b/pkg/toolkit/presenter/jsonapi/jsonapi.go @@ -7,8 +7,8 @@ import ( "sort" "strings" - goooerrors "github.com/version-1/gooo/pkg/errors" - "github.com/version-1/gooo/pkg/logger" + goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/version-1/gooo/pkg/toolkit/logger" ) type Resourcer interface { diff --git a/pkg/toolkit/presenter/jsonapi/stringify.go b/pkg/toolkit/presenter/jsonapi/stringify.go index 7bc639e..2542322 100644 --- a/pkg/toolkit/presenter/jsonapi/stringify.go +++ b/pkg/toolkit/presenter/jsonapi/stringify.go @@ -3,7 +3,7 @@ package jsonapi import ( "encoding/json" - goooerrors "github.com/version-1/gooo/pkg/errors" + goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" ) func Stringify(v any) string { diff --git a/pkg/toolkit/testing/cleaner/adapter/pq.go b/pkg/toolkit/testing/cleaner/adapter/pq.go index 5816fa8..eade7c6 100644 --- a/pkg/toolkit/testing/cleaner/adapter/pq.go +++ b/pkg/toolkit/testing/cleaner/adapter/pq.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/lib/pq" - "github.com/version-1/gooo/pkg/db" + "github.com/version-1/gooo/pkg/datasource/db" ) var excluded = pq.Array([]string{"schema_migrations"}) diff --git a/pkg/toolkit/testing/cleaner/cleaner.go b/pkg/toolkit/testing/cleaner/cleaner.go index 131c9da..08ad2b2 100644 --- a/pkg/toolkit/testing/cleaner/cleaner.go +++ b/pkg/toolkit/testing/cleaner/cleaner.go @@ -3,8 +3,8 @@ package cleaner import ( "context" - "github.com/version-1/gooo/pkg/db" - "github.com/version-1/gooo/pkg/testing/cleaner/adapter" + "github.com/version-1/gooo/pkg/datasource/db" + "github.com/version-1/gooo/pkg/toolkit/testing/cleaner/adapter" ) var _ CleanAdapter = (*adapter.Pq)(nil) From a1905002b4a13defd53ffcca2b7b18a9c7632a75 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 12:44:40 -0800 Subject: [PATCH 14/20] Add branch for ci --- .github/workflows/main.yaml | 50 ++---------------------------- pkg/toolkit/errors/errors_test.go | 4 +-- pkg/toolkit/payload/loader_test.go | 1 + 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 969d57e..568e03d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -7,6 +7,7 @@ on: - develop - 'feature/*' - 'releases/*' + - 'v0.1.0-20240104' jobs: test-packages: runs-on: ubuntu-latest @@ -39,52 +40,7 @@ jobs: run: apk add --update-cache git - name: Init Database run: go run test/cmd/initdb/main.go + - name: Go mod tidy + run: go mod tidy - name: Run tests run: go test -v ./pkg/... - run-examples: - runs-on: ubuntu-latest - container: - image: golang:1.22-alpine3.20 - defaults: - run: - shell: sh - env: - ENV: test - DATABASE_URL: postgres://gooo:password@db:5432/gooo_test?sslmode=disable - services: - db: - image: postgres:16.2 - env: - POSTGRES_USER: gooo - POSTGRES_PASSWORD: password - POSTGRES_DB: gooo_test - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - steps: - - name: Check out repository code - uses: actions/checkout@v4 - - name: Install dependencies - run: apk add --update-cache git - - name: Init Database - run: go run test/cmd/initdb/main.go - # - name: Run API - # run: go run examples/starter/cmd/api/main.go - - name: Run Seed - run: go run examples/starter/cmd/seed/main.go - - name: Run Migration Up - env: - MIGRATION_PATH: examples/starter/db/migrations/*.sql - run: go run examples/starter/cmd/migration/main.go up - - name: Run Migration Down - env: - MIGRATION_PATH: examples/starter/db/migrations/*.sql - run: go run examples/starter/cmd/migration/main.go down - - name: Run Migration Generate - run: go run examples/starter/cmd/migration/main.go generate test - - name: Run Test - run: go test ./examples/starter/... diff --git a/pkg/toolkit/errors/errors_test.go b/pkg/toolkit/errors/errors_test.go index cdf7081..56b5e7d 100644 --- a/pkg/toolkit/errors/errors_test.go +++ b/pkg/toolkit/errors/errors_test.go @@ -19,7 +19,7 @@ func TestErrors(t *testing.T) { }, Expect: func(t *testing.T) ([]string, error) { return []string{ - "gooo/pkg/errors/errors_test.go. method: TestErrors. line: 12", + "gooo/pkg/toolkit/errors/errors_test.go. method: TestErrors. line: 12", "src/testing/testing.go. method: tRunner. line: 1689", "src/runtime/asm_amd64.s. method: goexit. line: 1695", "", @@ -47,7 +47,7 @@ func TestErrors(t *testing.T) { return []string{ "pkg/errors : msg", "", - "gooo/pkg/errors/errors_test.go. method: TestErrors. line: 12", + "gooo/pkg/toolkit/errors/errors_test.go. method: TestErrors. line: 12", "src/testing/testing.go. method: tRunner. line: 1689", "src/runtime/asm_amd64.s. method: goexit. line: 1695", "", diff --git a/pkg/toolkit/payload/loader_test.go b/pkg/toolkit/payload/loader_test.go index d36e509..3dfb763 100644 --- a/pkg/toolkit/payload/loader_test.go +++ b/pkg/toolkit/payload/loader_test.go @@ -12,6 +12,7 @@ const ( ) func TestLoad(t *testing.T) { + t.Skip("skipping test in CI") loader := NewEnvfileLoader[ConfigKey]("./fixtures/.env.test") m, err := loader.Load() if err != nil { From 222111e9d435cae1b2ecf302917b13b1b91daa90 Mon Sep 17 00:00:00 2001 From: Jiro Date: Sun, 9 Feb 2025 13:06:01 -0800 Subject: [PATCH 15/20] some fix --- .github/workflows/main.yaml | 1 - gooo.go | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 gooo.go diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 568e03d..792b176 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -7,7 +7,6 @@ on: - develop - 'feature/*' - 'releases/*' - - 'v0.1.0-20240104' jobs: test-packages: runs-on: ubuntu-latest diff --git a/gooo.go b/gooo.go deleted file mode 100644 index efaf1ca..0000000 --- a/gooo.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("gooo") -} From 90aef579126b3553306f60d9fd717e0bf3f85a9e Mon Sep 17 00:00:00 2001 From: Jiro Date: Fri, 14 Mar 2025 13:56:58 -0700 Subject: [PATCH 16/20] mv errors pkg --- go.mod | 5 +- go.sum | 14 +- pkg/command/migration/adapter/yaml/schema.go | 2 +- pkg/command/migration/helper/helper.go | 2 +- pkg/command/migration/migration.go | 2 +- pkg/core/api/app/app.go | 2 +- pkg/core/generator/generator.go | 2 +- pkg/core/schema/template/main.go | 2 +- pkg/core/schema/template/route.go | 2 +- pkg/core/schema/template/schema.go | 2 +- pkg/toolkit/errors/errors.go | 159 ------------------- pkg/toolkit/errors/errors_test.go | 101 ------------ pkg/toolkit/presenter/jsonapi/jsonapi.go | 2 +- pkg/toolkit/presenter/jsonapi/stringify.go | 2 +- pkg/toolkit/testing/table.go | 30 ---- pkg/toolkit/util/util.go | 2 +- 16 files changed, 18 insertions(+), 313 deletions(-) delete mode 100644 pkg/toolkit/errors/errors.go delete mode 100644 pkg/toolkit/errors/errors_test.go delete mode 100644 pkg/toolkit/testing/table.go diff --git a/go.mod b/go.mod index 23f1e3a..10bb368 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,14 @@ go 1.22.3 require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 + github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 - github.com/stretchr/testify v1.10.0 golang.org/x/tools v0.23.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 2c17359..0389c25 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,23 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gooolib/errors v0.0.0-20250314203810-d2ce146c3a5b h1:omD2l8xSlfbK0QsXu/XBoVJ/uZdBE/CzonTO0oj1YVI= +github.com/gooolib/errors v0.0.0-20250314203810-d2ce146c3a5b/go.mod h1:KgSNO9qMkI+sK5ebzTFDwLB7S1rc/AkVyjlvR4mnHrE= +github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980 h1:a3xmBXwQwrWNtNjRPjScTx0n1bbRIuPguq2Ji/wSJEE= +github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980/go.mod h1:KgSNO9qMkI+sK5ebzTFDwLB7S1rc/AkVyjlvR4mnHrE= +github.com/gooolib/testing v0.0.0-20250314201531-08ff6e6b2f90 h1:of8hfCY7cwDtlzpUN8NzO+tN3GL6D8BibzkZOBNOI3w= +github.com/gooolib/testing v0.0.0-20250314201531-08ff6e6b2f90/go.mod h1:jPlQfVHD4u0YRBx4DCufwvriNX8ykr+i4zqPMadgkAs= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= diff --git a/pkg/command/migration/adapter/yaml/schema.go b/pkg/command/migration/adapter/yaml/schema.go index 9fb33a5..c26cdae 100644 --- a/pkg/command/migration/adapter/yaml/schema.go +++ b/pkg/command/migration/adapter/yaml/schema.go @@ -8,7 +8,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/datasource/db" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" yaml "gopkg.in/yaml.v3" ) diff --git a/pkg/command/migration/helper/helper.go b/pkg/command/migration/helper/helper.go index b6e33c3..2b74db6 100644 --- a/pkg/command/migration/helper/helper.go +++ b/pkg/command/migration/helper/helper.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + goooerrors "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/command/migration/constants" ) diff --git a/pkg/command/migration/migration.go b/pkg/command/migration/migration.go index c1e6b4c..895cf48 100644 --- a/pkg/command/migration/migration.go +++ b/pkg/command/migration/migration.go @@ -13,7 +13,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/runner" "github.com/version-1/gooo/pkg/datasource/db" - goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + goooerrors "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/core/api/app/app.go b/pkg/core/api/app/app.go index cf6bbc2..ff08266 100644 --- a/pkg/core/api/app/app.go +++ b/pkg/core/api/app/app.go @@ -6,7 +6,7 @@ import ( "time" "github.com/version-1/gooo/pkg/core/api/middleware" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/core/generator/generator.go b/pkg/core/generator/generator.go index 0150cf4..3fa6f4e 100644 --- a/pkg/core/generator/generator.go +++ b/pkg/core/generator/generator.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/toolkit/util" ) diff --git a/pkg/core/schema/template/main.go b/pkg/core/schema/template/main.go index 78f4b2c..5301877 100644 --- a/pkg/core/schema/template/main.go +++ b/pkg/core/schema/template/main.go @@ -6,7 +6,7 @@ import ( "text/template" "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" ) //go:embed components/*.go.tmpl diff --git a/pkg/core/schema/template/route.go b/pkg/core/schema/template/route.go index 54ee7d3..8e48173 100644 --- a/pkg/core/schema/template/route.go +++ b/pkg/core/schema/template/route.go @@ -8,7 +8,7 @@ import ( "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" ) type Route struct { diff --git a/pkg/core/schema/template/schema.go b/pkg/core/schema/template/schema.go index 95933ef..dd253b3 100644 --- a/pkg/core/schema/template/schema.go +++ b/pkg/core/schema/template/schema.go @@ -9,7 +9,7 @@ import ( "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" "github.com/version-1/gooo/pkg/core/schema/template/partial" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" ) type SchemaFile struct { diff --git a/pkg/toolkit/errors/errors.go b/pkg/toolkit/errors/errors.go deleted file mode 100644 index 577f8c7..0000000 --- a/pkg/toolkit/errors/errors.go +++ /dev/null @@ -1,159 +0,0 @@ -package errors - -import ( - "errors" - "fmt" - "runtime" -) - -type Error struct { - err error - stack *stack -} - -func Wrap(err error) *Error { - if err == nil { - return nil - } - - return &Error{ - err: err, - stack: captureStack(), - } -} - -func New(msg string) *Error { - return &Error{ - err: errors.New(msg), - stack: captureStack(), - } -} - -func Errorf(tmpl string, args ...any) *Error { - return &Error{ - err: errors.New(fmt.Sprintf(tmpl, args...)), - stack: captureStack(), - } -} - -func (e Error) StackTrace() string { - return fmt.Sprintf("%+v", e.stack) -} - -func (e Error) Error() string { - return fmt.Sprintf("pkg/errors : %s", e.err) -} - -func (e Error) Format(f fmt.State, c rune) { - switch c { - case 'v': - if f.Flag('+') { - fmt.Fprintf(f, "%s\n", e.Error()) - fmt.Fprintln(f, "") - fmt.Fprintf(f, "%+v\n", e.stack) - return - } else { - fmt.Fprintf(f, "%s", e.Error()) - return - } - case 's': - fmt.Fprintf(f, "%s", e.Error()) - } -} - -type stack []frame - -func (st *stack) Format(f fmt.State, c rune) { - switch c { - case 'v', 's': - for _, fr := range *st { - output := fr.String() - if output != "" { - fmt.Fprintln(f, output) - } - } - } -} - -type frame struct { - pc uintptr - line *int - file *string - name *string -} - -func (f frame) counter() uintptr { return uintptr(f.pc) - 1 } - -func (f *frame) collect() { - fn := runtime.FuncForPC(f.counter()) - if fn == nil { - return - } - - name := fn.Name() - f.name = &name - file, line := fn.FileLine(f.counter()) - - f.file = &file - f.line = &line -} - -func (f frame) String() string { - if f.file == nil { - f.collect() - } - - if f.file == nil { - return "" - } - - return fmt.Sprintf("%s. method: %s. line: %d", f.File(), f.FuncName(), f.Line()) -} - -func (f *frame) File() string { - if f.file != nil { - return *f.file - } - f.collect() - - return *f.file -} - -func (f *frame) Line() int { - if f.line != nil { - return *f.line - } - f.collect() - - return *f.line -} - -func (f *frame) FuncName() string { - n := func(s string) string { - for i := len(s) - 1; i > 0; i-- { - if s[i] == '.' { - return s[i+1:] - } - } - return s - } - if f.name != nil { - return n(*f.name) - } - - f.collect() - - return n(*f.name) -} - -func captureStack() *stack { - const depth = 32 - var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) - frames := make([]frame, n) - for _, pc := range pcs[0:n] { - frames = append(frames, frame{pc: pc}) - } - st := stack(frames) - return &st -} diff --git a/pkg/toolkit/errors/errors_test.go b/pkg/toolkit/errors/errors_test.go deleted file mode 100644 index 56b5e7d..0000000 --- a/pkg/toolkit/errors/errors_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package errors - -import ( - "fmt" - "strings" - "testing" - - goootesting "github.com/version-1/gooo/pkg/toolkit/testing" -) - -func TestErrors(t *testing.T) { - err := New("msg") - - test := goootesting.NewTable([]goootesting.Record[string, []string]{ - { - Name: "Stacktrace", - Subject: func(_t *testing.T) (string, error) { - return err.StackTrace(), nil - }, - Expect: func(t *testing.T) ([]string, error) { - return []string{ - "gooo/pkg/toolkit/errors/errors_test.go. method: TestErrors. line: 12", - "src/testing/testing.go. method: tRunner. line: 1689", - "src/runtime/asm_amd64.s. method: goexit. line: 1695", - "", - }, nil - }, - Assert: func(t *testing.T, r *goootesting.Record[string, []string]) bool { - e, _ := r.Expect(t) - s, _ := r.Subject(t) - lines := strings.Split(s, "\n") - for i, line := range lines { - if !strings.HasSuffix(line, e[i]) { - t.Errorf("Expected(line %d) %s to contain %s", i, line, e[i]) - return false - } - } - return true - }, - }, - { - Name: "Print Error with +v", - Subject: func(_t *testing.T) (string, error) { - return fmt.Sprintf("%+v", err), nil - }, - Expect: func(t *testing.T) ([]string, error) { - return []string{ - "pkg/errors : msg", - "", - "gooo/pkg/toolkit/errors/errors_test.go. method: TestErrors. line: 12", - "src/testing/testing.go. method: tRunner. line: 1689", - "src/runtime/asm_amd64.s. method: goexit. line: 1695", - "", - "", - }, nil - }, - Assert: func(t *testing.T, r *goootesting.Record[string, []string]) bool { - e, _ := r.Expect(t) - s, _ := r.Subject(t) - lines := strings.Split(s, "\n") - for i, line := range lines { - if !strings.HasSuffix(line, e[i]) { - t.Errorf("Expected(line %d) %s to contain %s", i, line, e[i]) - return false - } - } - return true - }, - }, - { - Name: "Print Error with v", - Subject: func(_t *testing.T) (string, error) { - return fmt.Sprintf("%v", err), nil - }, - Expect: func(t *testing.T) ([]string, error) { - return []string{"pkg/errors : msg"}, nil - }, - Assert: func(t *testing.T, r *goootesting.Record[string, []string]) bool { - e, _ := r.Expect(t) - s, _ := r.Subject(t) - return s == e[0] - }, - }, - { - Name: "Print Error with s", - Subject: func(_t *testing.T) (string, error) { - return fmt.Sprintf("%s", err), nil - }, - Expect: func(t *testing.T) ([]string, error) { - return []string{"pkg/errors : msg"}, nil - }, - Assert: func(t *testing.T, r *goootesting.Record[string, []string]) bool { - e, _ := r.Expect(t) - s, _ := r.Subject(t) - return s == e[0] - }, - }, - }) - - test.Run(t) -} diff --git a/pkg/toolkit/presenter/jsonapi/jsonapi.go b/pkg/toolkit/presenter/jsonapi/jsonapi.go index d97eb27..0fd77ed 100644 --- a/pkg/toolkit/presenter/jsonapi/jsonapi.go +++ b/pkg/toolkit/presenter/jsonapi/jsonapi.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + goooerrors "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/toolkit/logger" ) diff --git a/pkg/toolkit/presenter/jsonapi/stringify.go b/pkg/toolkit/presenter/jsonapi/stringify.go index 2542322..0354f23 100644 --- a/pkg/toolkit/presenter/jsonapi/stringify.go +++ b/pkg/toolkit/presenter/jsonapi/stringify.go @@ -3,7 +3,7 @@ package jsonapi import ( "encoding/json" - goooerrors "github.com/version-1/gooo/pkg/toolkit/errors" + goooerrors "github.com/gooolib/errors" ) func Stringify(v any) string { diff --git a/pkg/toolkit/testing/table.go b/pkg/toolkit/testing/table.go deleted file mode 100644 index 2a1e78c..0000000 --- a/pkg/toolkit/testing/table.go +++ /dev/null @@ -1,30 +0,0 @@ -package testing - -import "testing" - -type Record[A any, E any] struct { - Name string - Subject func(t *testing.T) (A, error) - Expect func(t *testing.T) (E, error) - Assert func(t *testing.T, r *Record[A, E]) bool -} - -type Table[A, E any] struct { - records []Record[A, E] -} - -func NewTable[A, E any](records []Record[A, E]) *Table[A, E] { - return &Table[A, E]{ - records: records, - } -} - -func (table *Table[A, E]) Run(test *testing.T) { - for _, record := range table.records { - test.Run(record.Name, func(t *testing.T) { - if !record.Assert(t, &record) { - t.Errorf("Test %s failed", record.Name) - } - }) - } -} diff --git a/pkg/toolkit/util/util.go b/pkg/toolkit/util/util.go index cce6c43..05fcd62 100644 --- a/pkg/toolkit/util/util.go +++ b/pkg/toolkit/util/util.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/google/uuid" - "github.com/version-1/gooo/pkg/toolkit/errors" + "github.com/gooolib/errors" ) func LookupGomodDirPath() (string, error) { From b9228b699e58c09628406b44b2b8df650366fff5 Mon Sep 17 00:00:00 2001 From: Jiro Date: Fri, 14 Mar 2025 16:42:52 -0700 Subject: [PATCH 17/20] mv logger pkg --- examples/bare/cmd/app.go | 2 +- examples/core/generated/main.go | 2 +- go.mod | 1 + go.sum | 4 +- pkg/command/migration/migration.go | 2 +- pkg/command/migration/runner/runner.go | 2 +- pkg/command/seeder/seeder.go | 7 +- pkg/core/api/app/app.go | 2 +- pkg/core/api/app/config.go | 2 +- pkg/core/api/request/query.go | 2 +- pkg/core/api/request/request.go | 2 +- pkg/datasource/db/db.go | 2 +- pkg/datasource/db/logger.go | 2 +- pkg/toolkit/logger/logger.go | 116 ----------------------- pkg/toolkit/middleware/middleware.go | 2 +- pkg/toolkit/presenter/jsonapi/jsonapi.go | 2 +- 16 files changed, 19 insertions(+), 133 deletions(-) delete mode 100644 pkg/toolkit/logger/logger.go diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index f5b6229..923f3cd 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -11,7 +11,7 @@ import ( "github.com/version-1/gooo/pkg/core/api/request" "github.com/version-1/gooo/pkg/core/api/response" "github.com/version-1/gooo/pkg/core/api/route" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" "github.com/version-1/gooo/pkg/toolkit/middleware" ) diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 1936242..44880e6 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -11,7 +11,7 @@ import ( "github.com/version-1/gooo/pkg/core/api/request" "github.com/version-1/gooo/pkg/core/api/response" "github.com/version-1/gooo/pkg/core/api/route" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) func main() { diff --git a/go.mod b/go.mod index 10bb368..155c729 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980 + github.com/gooolib/logger v0.0.0-20250314215253-c6a05722b917 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 golang.org/x/tools v0.23.0 diff --git a/go.sum b/go.sum index 0389c25..d140cb5 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,10 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gooolib/errors v0.0.0-20250314203810-d2ce146c3a5b h1:omD2l8xSlfbK0QsXu/XBoVJ/uZdBE/CzonTO0oj1YVI= -github.com/gooolib/errors v0.0.0-20250314203810-d2ce146c3a5b/go.mod h1:KgSNO9qMkI+sK5ebzTFDwLB7S1rc/AkVyjlvR4mnHrE= github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980 h1:a3xmBXwQwrWNtNjRPjScTx0n1bbRIuPguq2Ji/wSJEE= github.com/gooolib/errors v0.0.0-20250314205124-0769f4da9980/go.mod h1:KgSNO9qMkI+sK5ebzTFDwLB7S1rc/AkVyjlvR4mnHrE= +github.com/gooolib/logger v0.0.0-20250314215253-c6a05722b917 h1:eqjUwYerkUEzXvF9n7Fg0fwQiIaheVagkkqGalikkx8= +github.com/gooolib/logger v0.0.0-20250314215253-c6a05722b917/go.mod h1:ZJcIfwYA74i00FtxZ+aeQW0iWilfdhCnXLMhC8klGQI= github.com/gooolib/testing v0.0.0-20250314201531-08ff6e6b2f90 h1:of8hfCY7cwDtlzpUN8NzO+tN3GL6D8BibzkZOBNOI3w= github.com/gooolib/testing v0.0.0-20250314201531-08ff6e6b2f90/go.mod h1:jPlQfVHD4u0YRBx4DCufwvriNX8ykr+i4zqPMadgkAs= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= diff --git a/pkg/command/migration/migration.go b/pkg/command/migration/migration.go index 895cf48..0549321 100644 --- a/pkg/command/migration/migration.go +++ b/pkg/command/migration/migration.go @@ -14,7 +14,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/runner" "github.com/version-1/gooo/pkg/datasource/db" goooerrors "github.com/gooolib/errors" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) var _ Runner = (*runner.Yaml)(nil) diff --git a/pkg/command/migration/runner/runner.go b/pkg/command/migration/runner/runner.go index cf8296a..718142d 100644 --- a/pkg/command/migration/runner/runner.go +++ b/pkg/command/migration/runner/runner.go @@ -7,7 +7,7 @@ import ( "github.com/version-1/gooo/pkg/command/migration/constants" "github.com/version-1/gooo/pkg/command/migration/reader" "github.com/version-1/gooo/pkg/datasource/db" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type Base struct { diff --git a/pkg/command/seeder/seeder.go b/pkg/command/seeder/seeder.go index d8f33b8..52731d1 100644 --- a/pkg/command/seeder/seeder.go +++ b/pkg/command/seeder/seeder.go @@ -6,8 +6,8 @@ import ( _ "github.com/lib/pq" + "github.com/gooolib/logger" "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/toolkit/logger" ) type SeedExecutor struct { @@ -15,10 +15,10 @@ type SeedExecutor struct { } type Logger interface { + Debugf(format string, args ...any) Infof(format string, args ...any) Warnf(format string, args ...any) Errorf(format string, args ...any) - Fatalf(format string, args ...any) } type Config interface { @@ -66,7 +66,8 @@ func (s SeedExecutor) RunWith(tx *sqlx.Tx, name ...string) { func (s SeedExecutor) Run(name ...string) { db, err := sqlx.Connect("postgres", s.cfg.Connstr()) if err != nil { - s.logger().Fatalf(err.Error()) + s.logger().Errorf(err.Error()) + panic(err) } defer db.Close() diff --git a/pkg/core/api/app/app.go b/pkg/core/api/app/app.go index ff08266..8b72c64 100644 --- a/pkg/core/api/app/app.go +++ b/pkg/core/api/app/app.go @@ -7,7 +7,7 @@ import ( "github.com/version-1/gooo/pkg/core/api/middleware" "github.com/gooolib/errors" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type App struct { diff --git a/pkg/core/api/app/config.go b/pkg/core/api/app/config.go index 5d846c9..cf07780 100644 --- a/pkg/core/api/app/config.go +++ b/pkg/core/api/app/config.go @@ -1,7 +1,7 @@ package app import ( - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type Config struct { diff --git a/pkg/core/api/request/query.go b/pkg/core/api/request/query.go index 0466ea3..665b157 100644 --- a/pkg/core/api/request/query.go +++ b/pkg/core/api/request/query.go @@ -4,7 +4,7 @@ import ( "net/url" "strconv" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type Query struct { diff --git a/pkg/core/api/request/request.go b/pkg/core/api/request/request.go index f03dc1c..c448e26 100644 --- a/pkg/core/api/request/request.go +++ b/pkg/core/api/request/request.go @@ -7,7 +7,7 @@ import ( "net/http" "github.com/version-1/gooo/pkg/core/api/context" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type Void struct{} diff --git a/pkg/datasource/db/db.go b/pkg/datasource/db/db.go index 2346a1d..5597d69 100644 --- a/pkg/datasource/db/db.go +++ b/pkg/datasource/db/db.go @@ -7,7 +7,7 @@ import ( "github.com/google/uuid" "github.com/jmoiron/sqlx" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type QueryRunner interface { diff --git a/pkg/datasource/db/logger.go b/pkg/datasource/db/logger.go index 35d6bfb..f2db993 100644 --- a/pkg/datasource/db/logger.go +++ b/pkg/datasource/db/logger.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type QueryLogger interface { diff --git a/pkg/toolkit/logger/logger.go b/pkg/toolkit/logger/logger.go deleted file mode 100644 index 11cbed6..0000000 --- a/pkg/toolkit/logger/logger.go +++ /dev/null @@ -1,116 +0,0 @@ -package logger - -import ( - "fmt" - "log" - "time" -) - -type Logger interface { - Infof(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) -} - -type defaultLogger struct { - level LogLevel -} - -type LogLevel int - -const ( - LogLevelDebug LogLevel = iota - LogLevelInfo - LogLevelWarn - LogLevelError - LogLevelFatal -) - -var DefaultLogger = defaultLogger{ - level: LogLevelInfo, -} - -func InfoLabel() string { - return WithColor(Cyan, "[INFO]") -} - -func ErrorLabel() string { - return WithColor(Red, "[ERROR]") -} - -func WarnLabel() string { - return WithColor(Yellow, "[WARN]") -} - -func DateLabel() string { - return DateFormat(time.Now()) -} - -func DateFormat(t time.Time) string { - return WithColor(Gray, t.Format("2006-01-02T15:04:05 -0700")) -} - -func (l *defaultLogger) SetLevel(level LogLevel) { - l.level = level -} - -func (l defaultLogger) SInfof(format string, args ...any) string { - return fmt.Sprintf(fmt.Sprintf("%s %s %s\n", InfoLabel(), DateLabel(), format), args...) -} - -func (l defaultLogger) SErrorf(format string, args ...any) string { - return fmt.Sprintf(fmt.Sprintf("%s %s %s\n", ErrorLabel(), DateLabel(), format), args...) -} - -func (l defaultLogger) SWarnf(format string, args ...any) string { - return fmt.Sprintf(fmt.Sprintf("%s %s %s\n", WarnLabel(), DateLabel(), format), args...) -} - -func (l defaultLogger) Debugf(format string, args ...interface{}) { - if l.level >= LogLevelInfo { - return - } - fmt.Printf(l.SInfof(format, args...)) -} - -func (l defaultLogger) Infof(format string, args ...interface{}) { - if l.level > LogLevelInfo { - return - } - fmt.Printf(l.SInfof(format, args...)) -} - -func (l defaultLogger) Errorf(format string, args ...interface{}) { - if l.level > LogLevelError { - return - } - fmt.Printf(l.SErrorf(format, args...)) -} - -func (l defaultLogger) Warnf(format string, args ...interface{}) { - if l.level > LogLevelWarn { - return - } - fmt.Printf(l.SWarnf(format, args...)) -} - -func (l defaultLogger) Fatalf(format string, args ...interface{}) { - if l.level > LogLevelFatal { - return - } - log.Fatalf(l.SErrorf(format, args...)) -} - -func WithColor(c, msg string) string { - return fmt.Sprintf("%s%s%s", c, msg, Reset) -} - -var Reset = "\033[0m" -var Red = "\033[31m" -var Green = "\033[32m" -var Yellow = "\033[33m" -var Blue = "\033[34m" -var Magenta = "\033[35m" -var Cyan = "\033[36m" -var Gray = "\033[37m" -var White = "\033[97m" diff --git a/pkg/toolkit/middleware/middleware.go b/pkg/toolkit/middleware/middleware.go index 8bfec18..2cbd99d 100644 --- a/pkg/toolkit/middleware/middleware.go +++ b/pkg/toolkit/middleware/middleware.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/version-1/gooo/pkg/core/api/middleware" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) func RequestLogger(logger logger.Logger) middleware.Middleware { diff --git a/pkg/toolkit/presenter/jsonapi/jsonapi.go b/pkg/toolkit/presenter/jsonapi/jsonapi.go index 0fd77ed..15a4469 100644 --- a/pkg/toolkit/presenter/jsonapi/jsonapi.go +++ b/pkg/toolkit/presenter/jsonapi/jsonapi.go @@ -8,7 +8,7 @@ import ( "strings" goooerrors "github.com/gooolib/errors" - "github.com/version-1/gooo/pkg/toolkit/logger" + "github.com/gooolib/logger" ) type Resourcer interface { From 1b06e81e2bf5c7fb15df3fc3a2c6b29cc0110c6d Mon Sep 17 00:00:00 2001 From: Jiro Date: Mon, 24 Mar 2025 01:49:14 -0700 Subject: [PATCH 18/20] refactor: add routes file --- .../core/generated/internal/routes/routes.go | 49 ++++++++++++++++++ examples/core/generated/main.go | 50 ++----------------- pkg/core/schema/generate.go | 10 +++- .../schema/template/components/entry.go.tmpl | 13 ++--- .../schema/template/components/routes.go.tmpl | 19 +++++++ pkg/core/schema/template/main.go | 8 --- pkg/core/schema/template/route.go | 41 ++++++++++++++- 7 files changed, 125 insertions(+), 65 deletions(-) create mode 100644 examples/core/generated/internal/routes/routes.go create mode 100644 pkg/core/schema/template/components/routes.go.tmpl diff --git a/examples/core/generated/internal/routes/routes.go b/examples/core/generated/internal/routes/routes.go new file mode 100644 index 0000000..fa396b2 --- /dev/null +++ b/examples/core/generated/internal/routes/routes.go @@ -0,0 +1,49 @@ +package routes + +// This is a generated file. DO NOT EDIT manually. +import ( + "github.com/version-1/gooo/examples/core/generated/internal/schema" + "github.com/version-1/gooo/pkg/core/api/request" + "github.com/version-1/gooo/pkg/core/api/response" + "github.com/version-1/gooo/pkg/core/api/route" +) + +func Routes() []route.HandlerInterface { + routes := route.GroupHandler{ + Path: "/users", + Handlers: []route.HandlerInterface{ + route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something + }), + route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something + }), + route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), + route.JSON[schema.MutatePost, schema.Post]().Post("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something + }), + route.JSON[schema.MutatePost, schema.Post]().Patch("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Delete("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), + route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something + }), + }, + } + + return routes.Children() +} diff --git a/examples/core/generated/main.go b/examples/core/generated/main.go index 44880e6..1830832 100644 --- a/examples/core/generated/main.go +++ b/examples/core/generated/main.go @@ -6,12 +6,9 @@ import ( "log" "net/http" - "github.com/version-1/gooo/examples/core/generated/internal/schema" - "github.com/version-1/gooo/pkg/core/api/app" - "github.com/version-1/gooo/pkg/core/api/request" - "github.com/version-1/gooo/pkg/core/api/response" - "github.com/version-1/gooo/pkg/core/api/route" "github.com/gooolib/logger" + "github.com/version-1/gooo/examples/core/generated/internal/routes" + "github.com/version-1/gooo/pkg/core/api/app" ) func main() { @@ -28,48 +25,11 @@ func main() { }, } - RegisterRoutes(server) + routeList := routes.Routes() + app.WithDefaultMiddlewares(server, routeList...) + ctx := context.Background() if err := server.Run(ctx); err != nil { log.Fatalf("failed to run app: %s", err) } } - -func RegisterRoutes(srv *app.App) { - routes := route.GroupHandler{ - Path: "/users", - Handlers: []route.HandlerInterface{ - route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { - // do something - }), - route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { - // do something - }), - route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), - route.JSON[schema.MutatePost, schema.Post]().Post("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { - // do something - }), - route.JSON[schema.MutatePost, schema.Post]().Patch("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Delete("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), - }, - } - app.WithDefaultMiddlewares(srv, routes.Children()...) -} diff --git a/pkg/core/schema/generate.go b/pkg/core/schema/generate.go index 811f2d4..e048f94 100644 --- a/pkg/core/schema/generate.go +++ b/pkg/core/schema/generate.go @@ -22,10 +22,18 @@ func NewGenerator(r *v3_0_0.RootSchema, outDir string, baseURL string) *Generato func (g *Generator) Generate() error { schemaFile := template.SchemaFile{Schema: g.r, PackageName: "schema"} + routesFile := template.RoutesFile{Schema: g.r, PackageName: "routes"} + routesFile.Dependencies = []string{ + fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(schemaFile.Filename())), + } + mainFile := template.Main{Schema: g.r} - mainFile.Dependencies = []string{fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(schemaFile.Filename()))} + mainFile.Dependencies = []string{ + fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(routesFile.Filename())), + } + g.outputs = append(g.outputs, routesFile) g.outputs = append(g.outputs, schemaFile) g.outputs = append(g.outputs, mainFile) diff --git a/pkg/core/schema/template/components/entry.go.tmpl b/pkg/core/schema/template/components/entry.go.tmpl index 6697103..4a6c74b 100644 --- a/pkg/core/schema/template/components/entry.go.tmpl +++ b/pkg/core/schema/template/components/entry.go.tmpl @@ -21,19 +21,12 @@ func main() { }, } - RegisterRoutes(server) + routeList := routes.Routes() + app.WithDefaultMiddlewares(server, routeList...) + ctx := context.Background() if err := server.Run(ctx); err != nil { log.Fatalf("failed to run app: %s", err) } } -func RegisterRoutes(srv *app.App) { - routes := route.GroupHandler{ - Path: "/users", - Handlers: []route.HandlerInterface{ - {{ .Routes }} - }, - } - app.WithDefaultMiddlewares(srv, routes.Children()...) -} diff --git a/pkg/core/schema/template/components/routes.go.tmpl b/pkg/core/schema/template/components/routes.go.tmpl new file mode 100644 index 0000000..97fa434 --- /dev/null +++ b/pkg/core/schema/template/components/routes.go.tmpl @@ -0,0 +1,19 @@ +package routes + +// This is a generated file. DO NOT EDIT manually. +import ( + {{ range .Dependencies }} + "{{ . }}" + {{ end }} +) + +func Routes() []route.HandlerInterface { + routes := route.GroupHandler{ + Path: "/users", + Handlers: []route.HandlerInterface{ + {{ .Routes }} + }, + } + + return routes.Children() +} diff --git a/pkg/core/schema/template/main.go b/pkg/core/schema/template/main.go index 5301877..de81dc4 100644 --- a/pkg/core/schema/template/main.go +++ b/pkg/core/schema/template/main.go @@ -15,7 +15,6 @@ var tmpl embed.FS type Main struct { Schema *v3_0_0.RootSchema Dependencies []string - Routes string } func (m Main) Filename() string { @@ -23,13 +22,6 @@ func (m Main) Filename() string { } func (m Main) Render() (string, error) { - routes, err := renderRoutes(extractRoutes(m.Schema)) - if err != nil { - return "", err - } - - m.Routes = routes - tmpl := template.Must(template.New("entry").ParseFS(tmpl, "components/entry.go.tmpl")) var b bytes.Buffer if err := tmpl.ExecuteTemplate(&b, "entry.go.tmpl", m); err != nil { diff --git a/pkg/core/schema/template/route.go b/pkg/core/schema/template/route.go index 8e48173..a0c0eac 100644 --- a/pkg/core/schema/template/route.go +++ b/pkg/core/schema/template/route.go @@ -6,11 +6,50 @@ import ( "strings" "text/template" + "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" - "github.com/gooolib/errors" ) +type RoutesFile struct { + Schema *v3_0_0.RootSchema + PackageName string + Dependencies []string +} + +func (r RoutesFile) Filename() string { + return "internal/routes/routes" +} + +func (r RoutesFile) Render() (string, error) { + routes := extractRoutes(r.Schema) + s, err := renderRoutes(routes) + if err != nil { + return "", err + } + + p := struct { + Routes string + Dependencies []string + }{ + Routes: s, + Dependencies: r.Dependencies, + } + + var b bytes.Buffer + tmpl := template.Must(template.New("routes").ParseFS(tmpl, "components/routes.go.tmpl")) + if err := tmpl.ExecuteTemplate(&b, "routes.go.tmpl", p); err != nil { + return "", errors.Wrap(err) + } + + res, err := pretify(r.Filename(), b.String()) + if err != nil { + return "", errors.Wrap(err) + } + + return string(res), nil +} + type Route struct { InputType string OutputType string From 54345f8519f9b0eae7633e354b2853303bf97f1e Mon Sep 17 00:00:00 2001 From: Jiro Date: Tue, 25 Mar 2025 07:02:59 -0700 Subject: [PATCH 19/20] feat: add routeimplements file --- examples/core/generated/{ => cmd}/main.go | 0 .../internal/routes/routeimplments.go | 47 ++++++ .../core/generated/internal/routes/routes.go | 101 +++++++---- pkg/core/schema/generate.go | 7 +- pkg/core/schema/template/common.go | 29 ++++ .../template/components/common/plain.go.tmpl | 10 ++ .../schema/template/components/route.go.tmpl | 3 - .../template/components/routehandler.go.tmpl | 6 + .../template/components/routeimpl.go.tmpl | 4 + .../schema/template/components/routes.go.tmpl | 3 + pkg/core/schema/template/main.go | 4 +- pkg/core/schema/template/route.go | 157 ++++++++++++------ pkg/core/schema/template/route/route.go | 58 +++++++ 13 files changed, 343 insertions(+), 86 deletions(-) rename examples/core/generated/{ => cmd}/main.go (100%) create mode 100644 examples/core/generated/internal/routes/routeimplments.go create mode 100644 pkg/core/schema/template/common.go create mode 100644 pkg/core/schema/template/components/common/plain.go.tmpl delete mode 100644 pkg/core/schema/template/components/route.go.tmpl create mode 100644 pkg/core/schema/template/components/routehandler.go.tmpl create mode 100644 pkg/core/schema/template/components/routeimpl.go.tmpl create mode 100644 pkg/core/schema/template/route/route.go diff --git a/examples/core/generated/main.go b/examples/core/generated/cmd/main.go similarity index 100% rename from examples/core/generated/main.go rename to examples/core/generated/cmd/main.go diff --git a/examples/core/generated/internal/routes/routeimplments.go b/examples/core/generated/internal/routes/routeimplments.go new file mode 100644 index 0000000..1e9197f --- /dev/null +++ b/examples/core/generated/internal/routes/routeimplments.go @@ -0,0 +1,47 @@ +package routes + +import ( + "github.com/version-1/gooo/examples/core/generated/internal/schema" + "github.com/version-1/gooo/pkg/core/api/request" + "github.com/version-1/gooo/pkg/core/api/response" +) + +func GetUsers(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something +} + +func PostUsers(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something +} + +func PatchUsersId(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + // do something +} + +func DeleteUsersId(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something +} + +func GetUsersId(res *response.Response[schema.User], req *request.Request[request.Void]) { + // do something +} + +func GetPosts(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something +} + +func PostPosts(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something +} + +func PatchPostsId(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + // do something +} + +func DeletePostsId(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something +} + +func GetPostsId(res *response.Response[schema.Post], req *request.Request[request.Void]) { + // do something +} diff --git a/examples/core/generated/internal/routes/routes.go b/examples/core/generated/internal/routes/routes.go index fa396b2..d4fb8b6 100644 --- a/examples/core/generated/internal/routes/routes.go +++ b/examples/core/generated/internal/routes/routes.go @@ -8,40 +8,81 @@ import ( "github.com/version-1/gooo/pkg/core/api/route" ) +func PostUsersHandler() route.HandlerInterface { + return route.JSON[schema.MutateUser, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + PostUsers(res, req) + }) +} + +func GetUsersHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + GetUsers(res, req) + }) +} + +func GetUsersIdHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + GetUsersId(res, req) + }) +} + +func PatchUsersIdHandler() route.HandlerInterface { + return route.JSON[schema.MutateUser, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { + PatchUsersId(res, req) + }) +} + +func DeleteUsersIdHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { + DeleteUsersId(res, req) + }) +} + +func GetPostsHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + GetPosts(res, req) + }) +} + +func PostPostsHandler() route.HandlerInterface { + return route.JSON[schema.MutatePost, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + PostPosts(res, req) + }) +} + +func GetPostsIdHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + GetPostsId(res, req) + }) +} + +func PatchPostsIdHandler() route.HandlerInterface { + return route.JSON[schema.MutatePost, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { + PatchPostsId(res, req) + }) +} + +func DeletePostsIdHandler() route.HandlerInterface { + return route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { + DeletePostsId(res, req) + }) +} + func Routes() []route.HandlerInterface { routes := route.GroupHandler{ Path: "/users", + Handlers: []route.HandlerInterface{ - route.JSON[request.Void, schema.User]().Get("/users", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[schema.MutateUser, schema.User]().Post("/users", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { - // do something - }), - route.JSON[schema.MutateUser, schema.User]().Patch("/users/{id}", func(res *response.Response[schema.User], req *request.Request[schema.MutateUser]) { - // do something - }), - route.JSON[request.Void, schema.User]().Delete("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.User]().Get("/users/{id}", func(res *response.Response[schema.User], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Get("/posts", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), - route.JSON[schema.MutatePost, schema.Post]().Post("/posts", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { - // do something - }), - route.JSON[schema.MutatePost, schema.Post]().Patch("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[schema.MutatePost]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Delete("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), - route.JSON[request.Void, schema.Post]().Get("/posts/{id}", func(res *response.Response[schema.Post], req *request.Request[request.Void]) { - // do something - }), + PostUsersHandler(), + GetUsersHandler(), + GetUsersIdHandler(), + PatchUsersIdHandler(), + DeleteUsersIdHandler(), + GetPostsHandler(), + PostPostsHandler(), + GetPostsIdHandler(), + PatchPostsIdHandler(), + DeletePostsIdHandler(), }, } diff --git a/pkg/core/schema/generate.go b/pkg/core/schema/generate.go index e048f94..5f6066e 100644 --- a/pkg/core/schema/generate.go +++ b/pkg/core/schema/generate.go @@ -22,6 +22,10 @@ func NewGenerator(r *v3_0_0.RootSchema, outDir string, baseURL string) *Generato func (g *Generator) Generate() error { schemaFile := template.SchemaFile{Schema: g.r, PackageName: "schema"} + routeImplementsFile := template.RouteImplementsFile{Schema: g.r, PackageName: "routes"} + routeImplementsFile.Dependencies = []string{ + fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(schemaFile.Filename())), + } routesFile := template.RoutesFile{Schema: g.r, PackageName: "routes"} routesFile.Dependencies = []string{ fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(schemaFile.Filename())), @@ -33,8 +37,9 @@ func (g *Generator) Generate() error { fmt.Sprintf("%s/%s", g.baseURL, filepath.Dir(routesFile.Filename())), } - g.outputs = append(g.outputs, routesFile) g.outputs = append(g.outputs, schemaFile) + g.outputs = append(g.outputs, routeImplementsFile) + g.outputs = append(g.outputs, routesFile) g.outputs = append(g.outputs, mainFile) for _, tmpl := range g.outputs { diff --git a/pkg/core/schema/template/common.go b/pkg/core/schema/template/common.go new file mode 100644 index 0000000..84d379b --- /dev/null +++ b/pkg/core/schema/template/common.go @@ -0,0 +1,29 @@ +package template + +import ( + "bytes" + "embed" + "text/template" + + "github.com/gooolib/errors" +) + +//go:embed components/common/*.go.tmpl +var commonTmpl embed.FS + +type CommonPlainTemplateParams struct { + Package string + HeadComments string + Dependencies []string + Content string +} + +func (p CommonPlainTemplateParams) Render() (string, error) { + var b bytes.Buffer + tmpl := template.Must(template.New("plain").ParseFS(commonTmpl, "components/common/plain.go.tmpl")) + if err := tmpl.ExecuteTemplate(&b, "plain.go.tmpl", p); err != nil { + return "", errors.Wrap(err) + } + + return b.String(), nil +} diff --git a/pkg/core/schema/template/components/common/plain.go.tmpl b/pkg/core/schema/template/components/common/plain.go.tmpl new file mode 100644 index 0000000..ef47642 --- /dev/null +++ b/pkg/core/schema/template/components/common/plain.go.tmpl @@ -0,0 +1,10 @@ +package {{ .Package }} + +{{ .HeadComments }} +import ( + {{ range .Dependencies }} + "{{ . }}" + {{ end }} +) + +{{ .Content }} diff --git a/pkg/core/schema/template/components/route.go.tmpl b/pkg/core/schema/template/components/route.go.tmpl deleted file mode 100644 index 5495507..0000000 --- a/pkg/core/schema/template/components/route.go.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -route.JSON[{{.InputType}}, {{.OutputType}}]().{{.Method}}("{{.Path}}", func(res *response.Response[{{.OutputType}}], req *request.Request[{{.InputType}}]) { - // do something -}), diff --git a/pkg/core/schema/template/components/routehandler.go.tmpl b/pkg/core/schema/template/components/routehandler.go.tmpl new file mode 100644 index 0000000..33c8477 --- /dev/null +++ b/pkg/core/schema/template/components/routehandler.go.tmpl @@ -0,0 +1,6 @@ +func {{ .FuncName }}Handler() route.HandlerInterface { + return route.JSON[{{.InputType}}, {{.OutputType}}]().Get("{{.Path}}", func(res *response.Response[{{ .OutputType }}], req *request.Request[{{ .InputType }}]) { + {{ .FuncName }}(res, req) + }) +} + diff --git a/pkg/core/schema/template/components/routeimpl.go.tmpl b/pkg/core/schema/template/components/routeimpl.go.tmpl new file mode 100644 index 0000000..a3ca4c9 --- /dev/null +++ b/pkg/core/schema/template/components/routeimpl.go.tmpl @@ -0,0 +1,4 @@ +func {{ .FuncName }}(res *response.Response[{{.OutputType}}], req *request.Request[{{.InputType}}]) { + // do something +} + diff --git a/pkg/core/schema/template/components/routes.go.tmpl b/pkg/core/schema/template/components/routes.go.tmpl index 97fa434..ffab122 100644 --- a/pkg/core/schema/template/components/routes.go.tmpl +++ b/pkg/core/schema/template/components/routes.go.tmpl @@ -7,9 +7,12 @@ import ( {{ end }} ) +{{ .RouteHandlers }} + func Routes() []route.HandlerInterface { routes := route.GroupHandler{ Path: "/users", + Handlers: []route.HandlerInterface{ {{ .Routes }} }, diff --git a/pkg/core/schema/template/main.go b/pkg/core/schema/template/main.go index de81dc4..a559bb7 100644 --- a/pkg/core/schema/template/main.go +++ b/pkg/core/schema/template/main.go @@ -5,8 +5,8 @@ import ( "embed" "text/template" - "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" "github.com/gooolib/errors" + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" ) //go:embed components/*.go.tmpl @@ -18,7 +18,7 @@ type Main struct { } func (m Main) Filename() string { - return "main" + return "cmd/main" } func (m Main) Render() (string, error) { diff --git a/pkg/core/schema/template/route.go b/pkg/core/schema/template/route.go index a0c0eac..ac4883f 100644 --- a/pkg/core/schema/template/route.go +++ b/pkg/core/schema/template/route.go @@ -2,38 +2,77 @@ package template import ( "bytes" - "strconv" - "strings" + "fmt" "text/template" "github.com/gooolib/errors" "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" - "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" + "github.com/version-1/gooo/pkg/core/schema/template/route" ) -type RoutesFile struct { +type RouteImplementsFile struct { Schema *v3_0_0.RootSchema PackageName string Dependencies []string } -func (r RoutesFile) Filename() string { - return "internal/routes/routes" +func (r RouteImplementsFile) Filename() string { + return "internal/routes/routeimplments" } -func (r RoutesFile) Render() (string, error) { +type RouteImplementsTemplateParams struct { + Functions string +} + +func (r RouteImplementsFile) Render() (string, error) { routes := extractRoutes(r.Schema) - s, err := renderRoutes(routes) + implString, err := renderRouteImplements(routes) if err != nil { return "", err } - p := struct { - Routes string - Dependencies []string - }{ - Routes: s, + p := CommonPlainTemplateParams{ + Package: r.PackageName, Dependencies: r.Dependencies, + Content: implString, + } + + content, err := p.Render() + + res, err := pretify(r.Filename(), content) + if err != nil { + return "", errors.Wrap(err) + } + + return string(res), nil +} + +type RoutesFile struct { + Schema *v3_0_0.RootSchema + PackageName string + Dependencies []string +} + +type RoutesTemplateParams struct { + Routes string + RouteHandlers string + RouteImplements string + Dependencies []string +} + +func (r RoutesFile) Filename() string { + return "internal/routes/routes" +} + +func (r RoutesFile) Render() (string, error) { + routes := extractRoutes(r.Schema) + handlersString, err := renderRouteHandlers(routes) + routeString := renderRoutes(routes) + + p := RoutesTemplateParams{ + Routes: routeString, + RouteHandlers: handlersString, + Dependencies: r.Dependencies, } var b bytes.Buffer @@ -51,23 +90,69 @@ func (r RoutesFile) Render() (string, error) { } type Route struct { + Name string InputType string OutputType string Method string Path string } -func renderRoutes(routes []Route) (string, error) { +func renderRouteHandlers(routes []Route) (string, error) { var b bytes.Buffer for _, r := range routes { - tmpl := template.Must(template.New("route").ParseFS(tmpl, "components/route.go.tmpl")) - if err := tmpl.ExecuteTemplate(&b, "route.go.tmpl", r); err != nil { + tmpl := template.Must(template.New("route").ParseFS(tmpl, "components/routehandler.go.tmpl")) + p := struct { + FuncName string + Path string + InputType string + OutputType string + }{ + FuncName: r.Name, + Path: r.Path, + InputType: r.InputType, + OutputType: r.OutputType, + } + + if err := tmpl.ExecuteTemplate(&b, "routehandler.go.tmpl", p); err != nil { + return "", errors.Wrap(err) + } + } + + return b.String(), nil +} + +func renderRouteImplements(routes []Route) (string, error) { + var b bytes.Buffer + for _, r := range routes { + tmpl := template.Must(template.New("route").ParseFS(tmpl, "components/routeimpl.go.tmpl")) + p := struct { + FuncName string + Path string + InputType string + OutputType string + }{ + FuncName: r.Name, + Path: r.Path, + InputType: r.InputType, + OutputType: r.OutputType, + } + + if err := tmpl.ExecuteTemplate(&b, "routeimpl.go.tmpl", p); err != nil { return "", errors.Wrap(err) } } + return b.String(), nil } +func renderRoutes(routes []Route) string { + var b bytes.Buffer + for _, r := range routes { + b.WriteString(fmt.Sprintf("%sHandler(),\n", r.Name)) + } + return b.String() +} + func extractRoutes(r *v3_0_0.RootSchema) []Route { routes := []Route{} r.Paths.Each(func(path string, pathItem v3_0_0.PathItem) error { @@ -83,10 +168,12 @@ func extractRoutes(r *v3_0_0.RootSchema) []Route { continue } + routeName := route.ResolveRouteName(k, path, v) if k == "Get" || k == "Delete" { route := Route{ + Name: routeName, InputType: "request.Void", - OutputType: withSchemaPackageName(detectOutputType(v, 200, "application/json")), + OutputType: withSchemaPackageName(route.DetectOutputType(v, 200, "application/json")), Method: k, Path: path, } @@ -98,8 +185,9 @@ func extractRoutes(r *v3_0_0.RootSchema) []Route { statusCode = 201 } route := Route{ - InputType: withSchemaPackageName(detectInputType(v, "application/json")), - OutputType: withSchemaPackageName(detectOutputType(v, statusCode, "application/json")), + Name: routeName, + InputType: withSchemaPackageName(route.DetectInputType(v, "application/json")), + OutputType: withSchemaPackageName(route.DetectOutputType(v, statusCode, "application/json")), Method: k, Path: path, } @@ -112,34 +200,3 @@ func extractRoutes(r *v3_0_0.RootSchema) []Route { return routes } - -func detectInputType(op *v3_0_0.Operation, contentType string) string { - schema := op.RequestBody.Content.Get(contentType).Schema - ref := "" - if schema.Ref != "" { - ref = schema.Ref - } - - if schema.Items.Type == "array" && schema.Items.Ref != "" { - ref = schema.Items.Ref - } - - schemaName := strings.Replace(ref, "#/components/schemas/", "", 1) - return schemaName -} - -func detectOutputType(op *v3_0_0.Operation, statusCode int, contentType string) string { - responses := yaml.OrderedMap[v3_0_0.Response](op.Responses) - schema := responses.Get(strconv.Itoa(statusCode)).Content.Get(contentType).Schema - ref := "" - if schema.Ref != "" { - ref = schema.Ref - } - - if schema.Type == "array" && schema.Items.Ref != "" { - ref = schema.Items.Ref - } - - schemaName := strings.Replace(ref, "#/components/schemas/", "", 1) - return schemaName -} diff --git a/pkg/core/schema/template/route/route.go b/pkg/core/schema/template/route/route.go new file mode 100644 index 0000000..45b18c3 --- /dev/null +++ b/pkg/core/schema/template/route/route.go @@ -0,0 +1,58 @@ +package route + +import ( + "strconv" + gostrings "strings" + + "github.com/version-1/gooo/pkg/core/schema/openapi/v3_0_0" + "github.com/version-1/gooo/pkg/core/schema/openapi/yaml" + "github.com/version-1/gooo/pkg/toolkit/strings" +) + +func ResolveRouteName(method, path string, operation *v3_0_0.Operation) string { + if operation.OperationId != "" { + return strings.ToCamelCase(operation.OperationId) + } + + name := strings.ToPascalCase(method) + for _, p := range gostrings.Split(path, "/") { + if gostrings.HasPrefix(p, "{") && gostrings.HasSuffix(p, "}") { + name += strings.ToPascalCase(p[1 : len(p)-1]) + } else { + name += strings.ToPascalCase(p) + } + } + + return name +} + +func DetectInputType(op *v3_0_0.Operation, contentType string) string { + schema := op.RequestBody.Content.Get(contentType).Schema + ref := "" + if schema.Ref != "" { + ref = schema.Ref + } + + if schema.Items.Type == "array" && schema.Items.Ref != "" { + ref = schema.Items.Ref + } + + schemaName := gostrings.Replace(ref, "#/components/schemas/", "", 1) + return schemaName +} + +func DetectOutputType(op *v3_0_0.Operation, statusCode int, contentType string) string { + responses := yaml.OrderedMap[v3_0_0.Response](op.Responses) + schema := responses.Get(strconv.Itoa(statusCode)).Content.Get(contentType).Schema + ref := "" + if schema.Ref != "" { + ref = schema.Ref + } + + if schema.Type == "array" && schema.Items.Ref != "" { + ref = schema.Items.Ref + } + + schemaName := gostrings.Replace(ref, "#/components/schemas/", "", 1) + return schemaName +} From bc5aa4bdbcb5d8bccc05051cdb95490c2689b5bd Mon Sep 17 00:00:00 2001 From: Jiro Date: Tue, 25 Mar 2025 07:17:00 -0700 Subject: [PATCH 20/20] doc: add read me to examples dir --- examples/bare/README.md | 12 ++++++++++++ examples/bare/cmd/app.go | 2 +- examples/core/README.md | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 examples/bare/README.md create mode 100644 examples/core/README.md diff --git a/examples/bare/README.md b/examples/bare/README.md new file mode 100644 index 0000000..54a8377 --- /dev/null +++ b/examples/bare/README.md @@ -0,0 +1,12 @@ + +Examples of gooo api. + + +## How to run + +## Generate code from swagger.yml + +```bash +go run cmd/app.go +``` + diff --git a/examples/bare/cmd/app.go b/examples/bare/cmd/app.go index 923f3cd..e2d7fe8 100644 --- a/examples/bare/cmd/app.go +++ b/examples/bare/cmd/app.go @@ -6,12 +6,12 @@ import ( "net/http" "time" + "github.com/gooolib/logger" "github.com/version-1/gooo/examples/bare/internal/swagger" "github.com/version-1/gooo/pkg/core/api/app" "github.com/version-1/gooo/pkg/core/api/request" "github.com/version-1/gooo/pkg/core/api/response" "github.com/version-1/gooo/pkg/core/api/route" - "github.com/gooolib/logger" "github.com/version-1/gooo/pkg/toolkit/middleware" ) diff --git a/examples/core/README.md b/examples/core/README.md new file mode 100644 index 0000000..da6e44d --- /dev/null +++ b/examples/core/README.md @@ -0,0 +1,18 @@ + +Examples of gooo to generate code from a swagger.yml file. + + +## How to run + +## Generate code from swagger.yml + +```bash +go run cmd/app.go +``` + +## Run Server + +```bash + +go run generated/cmd/main.go +```