From 400690235fc4a57c83d0a8d287c79522d09e75e6 Mon Sep 17 00:00:00 2001 From: Subomi Oluwalana Date: Tue, 10 Oct 2023 19:28:26 +0100 Subject: [PATCH 1/2] feat: improved core api --- example/basic/go.mod | 2 +- example/basic/go.sum | 4 ++ example/basic/main.go | 2 +- example/basic/requestmigrations.go | 92 ++++++++++++------------------ requestmigrations.go | 50 ++++++++++------ 5 files changed, 73 insertions(+), 77 deletions(-) diff --git a/example/basic/go.mod b/example/basic/go.mod index d86bdd1..ebf42d7 100644 --- a/example/basic/go.mod +++ b/example/basic/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/gorilla/mux v1.8.0 + github.com/prometheus/client_golang v1.16.0 github.com/subomi/requestmigrations v0.1.0 ) @@ -13,7 +14,6 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect diff --git a/example/basic/go.sum b/example/basic/go.sum index 6a0917f..af981d9 100644 --- a/example/basic/go.sum +++ b/example/basic/go.sum @@ -4,6 +4,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -15,6 +16,7 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= @@ -23,6 +25,7 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -31,3 +34,4 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/example/basic/main.go b/example/basic/main.go index f6a4607..c57c14c 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -86,7 +86,7 @@ type API struct { func (a *API) ListUser(w http.ResponseWriter, r *http.Request) { // Generate a random Int type number between 1 and 10 - randNum := rand.Intn(3-1+1) + 1 + randNum := rand.Intn(2-1+1) + 1 time.Sleep(time.Duration(randNum) * time.Second) users, err := a.store.GetAll() diff --git a/example/basic/requestmigrations.go b/example/basic/requestmigrations.go index 0b01b50..321feb2 100644 --- a/example/basic/requestmigrations.go +++ b/example/basic/requestmigrations.go @@ -1,11 +1,9 @@ package main import ( - "bytes" "encoding/json" - "fmt" - "io" "net/http" + "net/url" "strings" "time" ) @@ -13,27 +11,22 @@ import ( // Migrations type combineNamesForUserMigration struct{} -func (c *combineNamesForUserMigration) ShouldMigrateRequest(r *http.Request) bool { - return false -} - -func (c *combineNamesForUserMigration) MigrateRequest(r *http.Request) error { - fmt.Println("migrating request...") - - return nil -} +func (c *combineNamesForUserMigration) ShouldMigrateConstraint( + url *url.URL, + method string, + data []byte, + isReq bool) bool { -func (c *combineNamesForUserMigration) ShouldMigrateResponse( - req *http.Request, - res *http.Response) bool { + isUserPath := url.Path == "/users" + isGetMethod := method == http.MethodGet + isValidType := isReq == false - isUserPath := req.URL.Path == "/users" - isGetMethod := req.Method == http.MethodGet - - return isUserPath && isGetMethod + return isUserPath && isGetMethod && isValidType } -func (c *combineNamesForUserMigration) MigrateResponse(r *http.Response) error { +func (c *combineNamesForUserMigration) Migrate( + body []byte, + h http.Header) ([]byte, http.Header, error) { type oldUser struct { UID string `json:"uid"` Email string `json:"email"` @@ -43,21 +36,16 @@ func (c *combineNamesForUserMigration) MigrateResponse(r *http.Response) error { UpdatedAt time.Time `json:"updated_at"` } - body, err := io.ReadAll(r.Body) - if err != nil { - return err - } - var res ServerResponse - err = json.Unmarshal(body, &res) + err := json.Unmarshal(body, &res) if err != nil { - return err + return nil, nil, err } var users []*oldUser20230501 err = json.Unmarshal(res.Data, &users) if err != nil { - return err + return nil, nil, err } var newUsers []*oldUser @@ -74,11 +62,10 @@ func (c *combineNamesForUserMigration) MigrateResponse(r *http.Response) error { body, err = generateSuccessResponse(&newUsers, "users retrieved successfully") if err != nil { - return err + return nil, nil, err } - r.Body = io.NopCloser(bytes.NewReader(body)) - return nil + return body, h, nil } type oldUser20230501 struct { @@ -93,40 +80,32 @@ type oldUser20230501 struct { type expandProfileForUserMigration struct{} -func (e *expandProfileForUserMigration) ShouldMigrateRequest(r *http.Request) bool { - return false -} - -func (e *expandProfileForUserMigration) MigrateRequest(r *http.Request) error { - return nil -} - -func (e *expandProfileForUserMigration) ShouldMigrateResponse( - req *http.Request, - res *http.Response) bool { +func (e *expandProfileForUserMigration) ShouldMigrateConstraint( + url *url.URL, + method string, + body []byte, + isReq bool) bool { - isUserPath := req.URL.Path == "/users" - isGetMethod := req.Method == http.MethodGet + isUserPath := url.Path == "/users" + isGetMethod := method == http.MethodGet + isValidType := isReq == false - return isUserPath && isGetMethod + return isUserPath && isGetMethod && isValidType } -func (e *expandProfileForUserMigration) MigrateResponse(r *http.Response) error { - body, err := io.ReadAll(r.Body) - if err != nil { - return err - } - +func (e *expandProfileForUserMigration) Migrate( + body []byte, + h http.Header) ([]byte, http.Header, error) { var res ServerResponse - err = json.Unmarshal(body, &res) + err := json.Unmarshal(body, &res) if err != nil { - return err + return nil, nil, err } var users []*User err = json.Unmarshal(res.Data, &users) if err != nil { - return err + return nil, nil, err } var newUsers []*oldUser20230501 @@ -144,9 +123,8 @@ func (e *expandProfileForUserMigration) MigrateResponse(r *http.Response) error body, err = generateSuccessResponse(&newUsers, "users retrieved successfully") if err != nil { - return err + return nil, nil, err } - r.Body = io.NopCloser(bytes.NewReader(body)) - return nil + return body, h, nil } diff --git a/requestmigrations.go b/requestmigrations.go index 18c5b5c..198bc9f 100644 --- a/requestmigrations.go +++ b/requestmigrations.go @@ -1,11 +1,13 @@ package requestmigrations import ( + "bytes" "errors" "fmt" "io" "net/http" "net/http/httptest" + "net/url" "sort" "sync" "time" @@ -37,11 +39,8 @@ type Migrations map[string][]Migration // Migration is the core interface each transformation in every version // needs to implement. type Migration interface { - ShouldMigrateRequest(req *http.Request) bool - MigrateRequest(req *http.Request) error - - ShouldMigrateResponse(req *http.Request, res *http.Response) bool - MigrateResponse(res *http.Response) error + Migrate(data []byte, header http.Header) ([]byte, http.Header, error) + ShouldMigrateConstraint(url *url.URL, method string, data []byte, isReq bool) bool } type GetUserHeaderFunc func(req *http.Request) (string, error) @@ -158,6 +157,7 @@ func (rm *RequestMigration) VersionAPI(next http.Handler) http.Handler { err = m.applyRequestMigrations(req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) + fmt.Println(err) return } @@ -238,6 +238,13 @@ func (m *Migrator) applyRequestMigrations(req *http.Request) error { return nil } + data, err := io.ReadAll(req.Body) + if err != nil { + return err + } + + header := req.Header.Clone() + for _, version := range m.versions { migrations, ok := m.migrations[version.String()] if !ok { @@ -250,17 +257,22 @@ func (m *Migrator) applyRequestMigrations(req *http.Request) error { } for _, migration := range migrations { - if !migration.ShouldMigrateRequest(req) { + if !migration.ShouldMigrateConstraint(req.URL, req.Method, data, true) { continue } - err := migration.MigrateRequest(req) + data, header, err = migration.Migrate(data, header) if err != nil { return err } } } + req.Header = header + + // set the body back for the rest of the middleware. + req.Body = io.NopCloser(bytes.NewReader(data)) + return nil } @@ -269,6 +281,13 @@ func (m *Migrator) applyResponseMigrations( rr *httptest.ResponseRecorder, w http.ResponseWriter) error { res := rr.Result() + data, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + header := res.Header.Clone() + for i := len(m.versions); i > 0; i-- { version := m.versions[i-1] migrations, ok := m.migrations[version.String()] @@ -282,18 +301,18 @@ func (m *Migrator) applyResponseMigrations( } for _, migration := range migrations { - if !migration.ShouldMigrateResponse(req, res) { + if !migration.ShouldMigrateConstraint(req.URL, req.Method, data, false) { continue } - err := migration.MigrateResponse(res) + data, header, err = migration.Migrate(data, header) if err != nil { return ErrServerError } } } - err := m.finalResponder(w, res) + err = m.finalResponder(w, data, header) if err != nil { // log error. return ErrServerError @@ -302,17 +321,12 @@ func (m *Migrator) applyResponseMigrations( return nil } -func (m *Migrator) finalResponder(w http.ResponseWriter, res *http.Response) error { - body, err := io.ReadAll(res.Body) - if err != nil { - return err - } - - for k, v := range res.Header { +func (m *Migrator) finalResponder(w http.ResponseWriter, body []byte, h http.Header) error { + for k, v := range h { w.Header()[k] = v } - _, err = w.Write(body) + _, err := w.Write(body) if err != nil { return err } From 104f9cd6a24a555573c2009309e6b8f2a68c8da5 Mon Sep 17 00:00:00 2001 From: Subomi Oluwalana Date: Tue, 10 Oct 2023 19:52:43 +0100 Subject: [PATCH 2/2] fix: fixed test; --- requestmigrations_test.go | 40 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/requestmigrations_test.go b/requestmigrations_test.go index 3e28a24..4099216 100644 --- a/requestmigrations_test.go +++ b/requestmigrations_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "strings" "testing" @@ -136,33 +137,27 @@ type oldUser struct { } type combineNamesMigration struct{} -func (c *combineNamesMigration) ShouldMigrateRequest(r *http.Request) bool { - return false -} - -func (c *combineNamesMigration) MigrateRequest(r *http.Request) error { - return nil -} +func (c *combineNamesMigration) ShouldMigrateConstraint( + url *url.URL, + method string, + body []byte, + isReq bool) bool { -func (c *combineNamesMigration) ShouldMigrateResponse( - req *http.Request, - res *http.Response) bool { - isUserPath := req.URL.Path == "/users" - isGetMethod := req.Method == http.MethodGet + isUserPath := url.Path == "/users" + isGetMethod := method == http.MethodGet + isValidType := isReq == false - return isUserPath && isGetMethod + return isUserPath && isGetMethod && isValidType } -func (c *combineNamesMigration) MigrateResponse(r *http.Response) error { - body, err := io.ReadAll(r.Body) - if err != nil { - return err - } +func (c *combineNamesMigration) Migrate( + body []byte, + h http.Header) ([]byte, http.Header, error) { var newuser user - err = json.Unmarshal(body, &newuser) + err := json.Unmarshal(body, &newuser) if err != nil { - return err + return nil, nil, err } var user oldUser @@ -171,9 +166,8 @@ func (c *combineNamesMigration) MigrateResponse(r *http.Response) error { body, err = json.Marshal(&user) if err != nil { - return err + return nil, nil, err } - r.Body = io.NopCloser(bytes.NewReader(body)) - return nil + return body, h, nil }