diff --git a/README.md b/README.md index 4ed28dd..7dbba68 100644 --- a/README.md +++ b/README.md @@ -12,41 +12,34 @@ ``` ## Usage -This package exposes primarily one API - `Migrate`. It is used to migrate and rollback changes to your request and response respectively. Here's a short exmaple: +This package exposes primarily one API - `Migrate`. It is used to migrate and rollback changes to your request and response respectively. Here's a short example: ```go package main func createUser(r *http.Request, w http.ResponseWriter) { - err, res, rollback := rm.Migrate(r, "createUser") + // Identify version and transform the request payload. + err, vw, rollback := rm.Migrate(r, "createUser") if err != nil { w.Write("Bad Request") } - defer rollback(w) - payload, err := io.ReadAll(r.Body) - if err != nil { - w.Write("Bad Request") - } + // Setup response transformation callback. + defer rollback(w) - var userObject user - err = json.Unmarshal(payload, &userObject) + // ...Perform core business logic... + data, err := createUserObject(body) if err != nil { - w.Write("Bad Request") - } - - userObject = user{ - Email: userObject.Email, - FirstName: userObject.FirstName, - LastName: userObject.LastName, + return err } - body, err := json.Marshal(userObject) + // Write response + body, err := json.Marshal(data) if err != nil { w.Write("Bad Request") } - res.SetBody(body) + vw.Write(body) } ``` diff --git a/example/advanced/main.go b/example/advanced/main.go new file mode 100644 index 0000000..ad14291 --- /dev/null +++ b/example/advanced/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("TODO: Write a more complex example") +} diff --git a/example/basic/helper/helper.go b/example/basic/helper/helper.go new file mode 100644 index 0000000..6ee8cef --- /dev/null +++ b/example/basic/helper/helper.go @@ -0,0 +1,29 @@ +package helper + +import "encoding/json" + +type ServerResponse struct { + Status bool `json:"status"` + Message string `json:"message"` + Data json.RawMessage `json:"data,omitempty"` +} + +func GenerateSuccessResponse(payload interface{}, message string) ([]byte, error) { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + s := &ServerResponse{ + Status: true, + Message: message, + Data: data, + } + + res, err := json.Marshal(s) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/example/basic/main.go b/example/basic/main.go index 67e6b52..6a4dc09 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -1,7 +1,9 @@ package main import ( - "encoding/json" + "basicexample/helper" + v20230401 "basicexample/v20230401" + v20230501 "basicexample/v20230501" "log" "math/rand" "net/http" @@ -27,12 +29,12 @@ func main() { log.Fatal(err) } - rm.RegisterMigrations(rms.Migrations{ + rm.RegisterMigrations(rms.MigrationStore{ "2023-05-01": []rms.Migration{ - &expandProfileForUserMigration{}, + &v20230501.ListUserResponseMigration{}, }, "2023-04-01": []rms.Migration{ - &combineNamesForUserMigration{}, + &v20230401.ListUserResponseMigration{}, }, }) @@ -55,8 +57,7 @@ func main() { func buildMux(api *API) http.Handler { m := mux.NewRouter() - m.Handle("/users", - api.rm.VersionAPI(http.HandlerFunc(api.ListUser))).Methods("GET") + m.HandleFunc("/users", api.ListUser).Methods("GET") m.HandleFunc("/users/{id}", api.GetUser).Methods("GET") reg := prometheus.NewRegistry() @@ -69,11 +70,6 @@ func buildMux(api *API) http.Handler { } // api models -type ServerResponse struct { - Status bool `json:"status"` - Message string `json:"message"` - Data json.RawMessage `json:"data,omitempty"` -} // define api type API struct { @@ -82,6 +78,14 @@ type API struct { } func (a *API) ListUser(w http.ResponseWriter, r *http.Request) { + err, vw, rollback := a.rm.Migrate(r, "ListUser") + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + defer rollback(w) + // Generate a random Int type number between 1 and 10 randNum := rand.Intn(2-1+1) + 1 time.Sleep(time.Duration(randNum) * time.Second) @@ -92,13 +96,13 @@ func (a *API) ListUser(w http.ResponseWriter, r *http.Request) { return } - res, err := generateSuccessResponse(users, "users retrieved successfully") + res, err := helper.GenerateSuccessResponse(users, "users retrieved successfully") if err != nil { w.WriteHeader(http.StatusBadGateway) return } - w.Write(res) + vw.Write(res) } func (a *API) GetUser(w http.ResponseWriter, r *http.Request) { @@ -109,7 +113,7 @@ func (a *API) GetUser(w http.ResponseWriter, r *http.Request) { return } - res, err := generateSuccessResponse(user, "user retrieved successfully") + res, err := helper.GenerateSuccessResponse(user, "user retrieved successfully") if err != nil { w.WriteHeader(http.StatusBadGateway) return @@ -117,24 +121,3 @@ func (a *API) GetUser(w http.ResponseWriter, r *http.Request) { w.Write(res) } - -// helpers -func generateSuccessResponse(payload interface{}, message string) ([]byte, error) { - data, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - s := &ServerResponse{ - Status: true, - Message: message, - Data: data, - } - - res, err := json.Marshal(s) - if err != nil { - return nil, err - } - - return res, nil -} diff --git a/example/basic/requestmigrations.go b/example/basic/requestmigrations.go index 321feb2..a9f6dab 100644 --- a/example/basic/requestmigrations.go +++ b/example/basic/requestmigrations.go @@ -1,6 +1,7 @@ package main import ( + "basicexample/helper" "encoding/json" "net/http" "net/url" @@ -9,9 +10,9 @@ import ( ) // Migrations -type combineNamesForUserMigration struct{} +type ListUserResponseMigration struct{} -func (c *combineNamesForUserMigration) ShouldMigrateConstraint( +func (c *ListUserResponseMigration) ShouldMigrateConstraint( url *url.URL, method string, data []byte, @@ -24,7 +25,7 @@ func (c *combineNamesForUserMigration) ShouldMigrateConstraint( return isUserPath && isGetMethod && isValidType } -func (c *combineNamesForUserMigration) Migrate( +func (c *ListUserResponseMigration) Migrate( body []byte, h http.Header) ([]byte, http.Header, error) { type oldUser struct { @@ -36,7 +37,7 @@ func (c *combineNamesForUserMigration) Migrate( UpdatedAt time.Time `json:"updated_at"` } - var res ServerResponse + var res helper.ServerResponse err := json.Unmarshal(body, &res) if err != nil { return nil, nil, err @@ -60,7 +61,7 @@ func (c *combineNamesForUserMigration) Migrate( newUsers = append(newUsers, &oldUser) } - body, err = generateSuccessResponse(&newUsers, "users retrieved successfully") + body, err = helper.GenerateSuccessResponse(&newUsers, "users retrieved successfully") if err != nil { return nil, nil, err } @@ -96,7 +97,7 @@ func (e *expandProfileForUserMigration) ShouldMigrateConstraint( func (e *expandProfileForUserMigration) Migrate( body []byte, h http.Header) ([]byte, http.Header, error) { - var res ServerResponse + var res helper.ServerResponse err := json.Unmarshal(body, &res) if err != nil { return nil, nil, err @@ -121,7 +122,7 @@ func (e *expandProfileForUserMigration) Migrate( newUsers = append(newUsers, &oldUser) } - body, err = generateSuccessResponse(&newUsers, "users retrieved successfully") + body, err = helper.GenerateSuccessResponse(&newUsers, "users retrieved successfully") if err != nil { return nil, nil, err } diff --git a/example/basic/v20230401/requestmigrations.go b/example/basic/v20230401/requestmigrations.go new file mode 100644 index 0000000..29066b2 --- /dev/null +++ b/example/basic/v20230401/requestmigrations.go @@ -0,0 +1,66 @@ +package v20230401 + +import ( + "basicexample/helper" + "encoding/json" + "net/http" + "strings" + "time" +) + +// Migrations +type ListUserResponseMigration struct{} + +func (c *ListUserResponseMigration) Migrate( + body []byte, + h http.Header) ([]byte, http.Header, error) { + type oldUser struct { + UID string `json:"uid"` + Email string `json:"email"` + FullName string `json:"full_name"` + Profile string `json:"profile"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + var res helper.ServerResponse + err := json.Unmarshal(body, &res) + if err != nil { + return nil, nil, err + } + + var users []*oldUser20230501 + err = json.Unmarshal(res.Data, &users) + if err != nil { + return nil, nil, err + } + + var newUsers []*oldUser + for _, u := range users { + var oldUser oldUser + oldUser.UID = u.UID + oldUser.Email = u.Email + oldUser.FullName = strings.Join([]string{u.FirstName, u.LastName}, " ") + oldUser.Profile = u.Profile + oldUser.CreatedAt = u.CreatedAt + oldUser.UpdatedAt = u.UpdatedAt + newUsers = append(newUsers, &oldUser) + } + + body, err = helper.GenerateSuccessResponse(&newUsers, "users retrieved successfully") + if err != nil { + return nil, nil, err + } + + return body, h, nil +} + +type oldUser20230501 struct { + UID string `json:"uid"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Profile string `json:"profile"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/example/basic/v20230501/requestmigrations.go b/example/basic/v20230501/requestmigrations.go new file mode 100644 index 0000000..75be5c2 --- /dev/null +++ b/example/basic/v20230501/requestmigrations.go @@ -0,0 +1,72 @@ +package v20230501 + +import ( + "basicexample/helper" + "encoding/json" + "net/http" + "time" +) + +type User struct { + UID string `json:"uid"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Profile *profile `json:"profile"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type profile struct { + UID string `json:"uid"` + GithubURL string `json:"github_url"` + TwitterURL string `json:"twitter_url"` +} + +// Migrations +type oldUser20230501 struct { + UID string `json:"uid"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Profile string `json:"profile"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ListUserResponseMigration struct{} + +func (e *ListUserResponseMigration) Migrate( + body []byte, h http.Header) ([]byte, http.Header, error) { + var res helper.ServerResponse + err := json.Unmarshal(body, &res) + if err != nil { + return nil, nil, err + } + + var users []*User + err = json.Unmarshal(res.Data, &users) + if err != nil { + return nil, nil, err + } + + var newUsers []*oldUser20230501 + for _, u := range users { + var oldUser oldUser20230501 + oldUser.UID = u.UID + oldUser.Email = u.Email + oldUser.FirstName = u.FirstName + oldUser.LastName = u.LastName + oldUser.Profile = u.Profile.UID + oldUser.CreatedAt = u.CreatedAt + oldUser.UpdatedAt = u.UpdatedAt + newUsers = append(newUsers, &oldUser) + } + + body, err = helper.GenerateSuccessResponse(&newUsers, "users retrieved successfully") + if err != nil { + return nil, nil, err + } + + return body, h, nil +} diff --git a/response.go b/response.go index 1aa8746..6341afa 100644 --- a/response.go +++ b/response.go @@ -8,7 +8,7 @@ type response struct { statusCode int } -func (r *response) SetBody(body []byte) { +func (r *response) Write(body []byte) { r.body = body }