Skip to content

Commit

Permalink
Merge pull request #15 from subomi/subomi/feat/refactor-migration-pre…
Browse files Browse the repository at this point in the history
…dicate

fix: improved migration matching logic
  • Loading branch information
subomi authored Jan 16, 2024
2 parents 6651e64 + 5d8a2b6 commit b47f28f
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 68 deletions.
87 changes: 56 additions & 31 deletions requestmigrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"errors"
"io"
"net/http"
"net/url"
"reflect"
"sort"
"strings"
"sync"
"time"

Expand All @@ -27,22 +28,24 @@ var (
ErrCurrentVersionCannotBeEmpty = errors.New("Current Version field cannot be empty")
)

// migrations := Migrations{
// "2023-02-28": []Migration{
// Migration{},
// Migration{},
// },
// }
type Migrations map[string][]Migration

// Migration is the core interface each transformation in every version
// needs to implement. It includes two predicate functions and two
// transformation functions.
type Migration interface {
Migrate(data []byte, header http.Header) ([]byte, http.Header, error)
ShouldMigrateConstraint(url *url.URL, method string, data []byte, isReq bool) bool
}

// Migrations is an array of migrations declared by each handler.
type Migrations []Migration

// migrations := Migrations{
// "2023-02-28": []Migration{
// Migration{},
// Migration{},
// },
// }
type MigrationStore map[string]Migrations

type GetUserVersionFunc func(req *http.Request) (string, error)

// RequestMigrationOptions is used to configure the RequestMigration type.
Expand Down Expand Up @@ -74,7 +77,7 @@ type RequestMigration struct {
iv string

mu sync.Mutex
migrations Migrations
migrations MigrationStore
}

func NewRequestMigration(opts *RequestMigrationOptions) (*RequestMigration, error) {
Expand All @@ -98,7 +101,7 @@ func NewRequestMigration(opts *RequestMigrationOptions) (*RequestMigration, erro
iv = "v0"
}

migrations := Migrations{
migrations := MigrationStore{
iv: []Migration{},
}

Expand All @@ -114,7 +117,7 @@ func NewRequestMigration(opts *RequestMigrationOptions) (*RequestMigration, erro
}, nil
}

func (rm *RequestMigration) RegisterMigrations(migrations Migrations) error {
func (rm *RequestMigration) RegisterMigrations(migrations MigrationStore) error {
rm.mu.Lock()
defer rm.mu.Unlock()

Expand All @@ -135,7 +138,7 @@ func (rm *RequestMigration) RegisterMigrations(migrations Migrations) error {
return nil
}

func (rm *RequestMigration) VersionRequest(r *http.Request) error {
func (rm *RequestMigration) VersionRequest(r *http.Request, handler string) error {
from, err := rm.getUserVersion(r)
if err != nil {
return err
Expand All @@ -154,15 +157,15 @@ func (rm *RequestMigration) VersionRequest(r *http.Request) error {
startTime := time.Now()
defer rm.observeRequestLatency(from, to, startTime)

err = m.applyRequestMigrations(r)
err = m.applyRequestMigrations(r, handler)
if err != nil {
return err
}

return nil
}

func (rm *RequestMigration) VersionResponse(r *http.Request, body []byte) ([]byte, error) {
func (rm *RequestMigration) VersionResponse(r *http.Request, body []byte, handler string) ([]byte, error) {
from, err := rm.getUserVersion(r)
if err != nil {
return nil, err
Expand All @@ -178,7 +181,7 @@ func (rm *RequestMigration) VersionResponse(r *http.Request, body []byte) ([]byt
return body, nil
}

return m.applyResponseMigrations(r, r.Header, body)
return m.applyResponseMigrations(r, r.Header, body, handler)
}

func (rm *RequestMigration) getUserVersion(req *http.Request) (*Version, error) {
Expand Down Expand Up @@ -241,10 +244,10 @@ type migrator struct {
to *Version
from *Version
versions []*Version
migrations Migrations
migrations MigrationStore
}

func Newmigrator(from, to *Version, avs []*Version, migrations Migrations) (*migrator, error) {
func Newmigrator(from, to *Version, avs []*Version, migrations MigrationStore) (*migrator, error) {
if !from.IsValid() || !to.IsValid() {
return nil, ErrInvalidVersion
}
Expand All @@ -265,7 +268,7 @@ func Newmigrator(from, to *Version, avs []*Version, migrations Migrations) (*mig
}, nil
}

func (m *migrator) applyRequestMigrations(req *http.Request) error {
func (m *migrator) applyRequestMigrations(req *http.Request, handler string) error {
if m.versions == nil {
return nil
}
Expand All @@ -288,11 +291,8 @@ func (m *migrator) applyRequestMigrations(req *http.Request) error {
continue
}

for _, migration := range migrations {
if !migration.ShouldMigrateConstraint(req.URL, req.Method, data, true) {
continue
}

migration := m.retrieveHandlerRequestMigration(migrations, handler)
if migration != nil {
data, header, err = migration.Migrate(data, header)
if err != nil {
return err
Expand All @@ -308,7 +308,7 @@ func (m *migrator) applyRequestMigrations(req *http.Request) error {
return nil
}

func (m *migrator) applyResponseMigrations(r *http.Request, header http.Header, data []byte) ([]byte, error) {
func (m *migrator) applyResponseMigrations(r *http.Request, header http.Header, data []byte, handler string) ([]byte, error) {
var err error

for i := len(m.versions); i > 0; i-- {
Expand All @@ -323,17 +323,42 @@ func (m *migrator) applyResponseMigrations(r *http.Request, header http.Header,
return data, nil
}

for _, migration := range migrations {
if !migration.ShouldMigrateConstraint(r.URL, r.Method, data, false) {
continue
}

migration := m.retrieveHandlerResponseMigration(migrations, handler)
if migration != nil {
data, _, err = migration.Migrate(data, header)
if err != nil {
return nil, ErrServerError
}
}

}

return data, nil
}

func (m *migrator) retrieveHandlerResponseMigration(migrations Migrations, handler string) Migration {
return m.retrieveHandlerMigration(migrations, strings.Join([]string{handler, "response"}, ""))
}

func (m *migrator) retrieveHandlerRequestMigration(migrations Migrations, handler string) Migration {
return m.retrieveHandlerMigration(migrations, strings.Join([]string{handler, "request"}, ""))
}

func (m *migrator) retrieveHandlerMigration(migrations Migrations, handler string) Migration {
for _, migration := range migrations {
var mv reflect.Value

mv = reflect.ValueOf(migration)

if mv.Kind() == reflect.Ptr {
mv = mv.Elem()
}

fName := strings.ToLower(mv.Type().Name())
if strings.HasPrefix(fName, strings.ToLower(handler)) {
return migration
}
}

return nil
}
74 changes: 37 additions & 37 deletions requestmigrations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

Expand Down Expand Up @@ -36,10 +35,11 @@ func newRequestMigration(t *testing.T) *RequestMigration {
}

func registerBasicMigrations(t *testing.T, rm *RequestMigration) {
migrations := &Migrations{
"2023-03-01": []Migration{
&combineNamesMigration{},
&splitNameMigration{},
migrations := &MigrationStore{
"2023-03-01": Migrations{
&getUserResponseCombineNamesMigration{},
&createUserRequestSplitNameMigration{},
&createUserResponseCombineNamesMigration{},
},
}

Expand All @@ -53,21 +53,10 @@ type oldUser struct {
Email string `json:"email"`
FullName string `json:"full_name"`
}
type combineNamesMigration struct{}

func (c *combineNamesMigration) ShouldMigrateConstraint(
url *url.URL,
method string,
body []byte,
isReq bool) bool {

isUserPath := url.Path == "/users"
isValidType := isReq == false

return isUserPath && isValidType
}
type getUserResponseCombineNamesMigration struct{}

func (c *combineNamesMigration) Migrate(
func (c *getUserResponseCombineNamesMigration) Migrate(
body []byte,
h http.Header) ([]byte, http.Header, error) {

Expand All @@ -89,22 +78,9 @@ func (c *combineNamesMigration) Migrate(
return body, h, nil
}

type splitNameMigration struct{}

func (c *splitNameMigration) ShouldMigrateConstraint(
url *url.URL,
method string,
body []byte,
isReq bool) bool {

isUserPath := url.Path == "/users"
isPostMethod := method == http.MethodPost
isValidType := isReq == true

return isUserPath && isPostMethod && isValidType
}
type createUserRequestSplitNameMigration struct{}

func (c *splitNameMigration) Migrate(
func (c *createUserRequestSplitNameMigration) Migrate(
body []byte,
h http.Header) ([]byte, http.Header, error) {

Expand All @@ -129,9 +105,33 @@ func (c *splitNameMigration) Migrate(
return body, h, nil
}

type createUserResponseCombineNamesMigration struct{}

func (c *createUserResponseCombineNamesMigration) Migrate(
body []byte,
h http.Header) ([]byte, http.Header, error) {

var newuser user
err := json.Unmarshal(body, &newuser)
if err != nil {
return nil, nil, err
}

var user oldUser
user.Email = newuser.Email
user.FullName = strings.Join([]string{newuser.FirstName, newuser.LastName}, " ")

body, err = json.Marshal(&user)
if err != nil {
return nil, nil, err
}

return body, h, nil
}

func createUser(t *testing.T, rm *RequestMigration) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := rm.VersionRequest(r)
err := rm.VersionRequest(r, "createUser")
if err != nil {
t.Fatal(err)
}
Expand All @@ -158,7 +158,7 @@ func createUser(t *testing.T, rm *RequestMigration) http.Handler {
t.Fatal(err)
}

resBody, err := rm.VersionResponse(r, body)
resBody, err := rm.VersionResponse(r, body, "createUser")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -255,7 +255,7 @@ func Test_VersionRequest(t *testing.T) {

func getUser(t *testing.T, rm *RequestMigration) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := rm.VersionRequest(r)
err := rm.VersionRequest(r, "getUser")
if err != nil {
t.Fatal(err)
}
Expand All @@ -271,7 +271,7 @@ func getUser(t *testing.T, rm *RequestMigration) http.Handler {
t.Fatal(err)
}

resBody, err := rm.VersionResponse(r, body)
resBody, err := rm.VersionResponse(r, body, "getUser")
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit b47f28f

Please sign in to comment.