diff --git a/internal/filter/filter_test.go b/internal/filter/filter_test.go index c22102a..d07476b 100644 --- a/internal/filter/filter_test.go +++ b/internal/filter/filter_test.go @@ -24,19 +24,19 @@ func Test_Filter(t *testing.T) { "1": { Identifier: "1", Data: map[string]interface{}{ - "filed": "val", + "field": "val", "num": "1", }, }, "2": { - Identifier: "1", + Identifier: "2", Data: map[string]interface{}{ "field": "value", "num": "2", }, }, "3": { - Identifier: "1", + Identifier: "3", Data: map[string]interface{}{ "field": "value", "num": "3", @@ -46,14 +46,14 @@ func Test_Filter(t *testing.T) { expectedData := map[model.Identifier]*model.Record{ "2": { - Identifier: "1", + Identifier: "2", Data: map[string]interface{}{ "field": "value", "num": "2", }, }, "3": { - Identifier: "1", + Identifier: "3", Data: map[string]interface{}{ "field": "value", "num": "3", @@ -61,6 +61,6 @@ func Test_Filter(t *testing.T) { }, } - data := f.Filter("pkey", testData) + data := f.Filter("num", testData) assert.Equal(t, &expectedData, &data) } diff --git a/internal/filter/filters.go b/internal/filter/filters.go index ce88578..86bbd80 100644 --- a/internal/filter/filters.go +++ b/internal/filter/filters.go @@ -44,18 +44,18 @@ func UseFilter(filter interface{}) (*Filter, *er.WdbError) { return &dataFilter, nil } -func filter(primaryKey model.Identifier, data map[model.Identifier]*model.Record, filter Filter, iterator func(model.Identifier, model.Record)) { +func filter(primaryKey model.Identifier, data map[model.Identifier]*model.Record, filter Filter, iterator func(*model.Identifier, *model.Record)) { if filter.Key == "id" || filter.Key == primaryKey.String() { // search with primaryKey/id d, exists := data[model.Identifier(filter.Value.(string))] if exists { - iterator(d.Identifier, *data[d.Identifier]) + iterator(&d.Identifier, data[d.Identifier]) } } else if filter.Key == "recordId" { // search with recordId for identifier, record := range data { if record.RecordId.String() == filter.Value { - iterator(identifier, *record) + iterator(&identifier, record) break } } @@ -65,25 +65,27 @@ func filter(primaryKey model.Identifier, data map[model.Identifier]*model.Record dataMap := record.DataMap() if exists, _ := fieldExists(filter.Key, dataMap); exists { if equal(dataMap[filter.Key], filter.Value) { - iterator(identifier, *record) + iterator(&identifier, record) } } } } } -func (f Filter) Filter(primaryKey model.Identifier, data map[model.Identifier]*model.Record) map[model.Identifier]*model.Record { +func (f Filter) Filter(primaryKey model.Identifier, data map[model.Identifier]*model.Record) (map[model.Identifier]*model.Record) { filteredData := make(map[model.Identifier]*model.Record) - filter(primaryKey, data, f, func(id model.Identifier, record model.Record) { - filteredData[id] = &record + filter(primaryKey, data, f, func(id *model.Identifier, record *model.Record) { + if id != nil && record != nil { + filteredData[*id] = record + } }) return filteredData } -func (f Filter) Iterate(primaryKey model.Identifier, data map[model.Identifier]*model.Record, iterator func(model.Identifier, model.Record)) { - filter(primaryKey, data, f, func(id model.Identifier, record model.Record) { +func (f Filter) Iterate(primaryKey model.Identifier, data map[model.Identifier]*model.Record, iterator func(*model.Identifier, *model.Record)) { + filter(primaryKey, data, f, func(id *model.Identifier, record *model.Record) { iterator(id, record) }) } diff --git a/internal/records/records.go b/internal/records/records.go index 28b6759..61211e4 100644 --- a/internal/records/records.go +++ b/internal/records/records.go @@ -76,6 +76,10 @@ func (r Records) Read(filters interface{}) (map[model.Identifier]*model.Record, } filteredData := f.Filter(*r.PrimaryKey, r.Data) + if len(filteredData) == 0 { + return nil, &er.RecordDoesNotExistsError + } + return filteredData, nil } @@ -83,57 +87,74 @@ func (r Records) Read(filters interface{}) (map[model.Identifier]*model.Record, } func (r Records) Update(updatedData interface{}, filters interface{}) *er.WdbError { + var recordsCount int = 0 + var f *filter.Filter + var err *er.WdbError + if filters == nil { return &er.FilterMissingError } - f, err := filter.UseFilter(filters) + f, err = filter.UseFilter(filters) if err != nil { return err } - var iterError *er.WdbError - - f.Iterate(*r.PrimaryKey, r.Data, func(identifier model.Identifier, dataRow model.Record) { - - data, err := maps.Merge(maps.Marshal(updatedData), dataRow.DataMap()) - if err != nil { - iterError = &er.DataEncodeDecodeError - } else { - schema, err := schema.UseSchema(r.Schema) - - if err != nil { - iterError = err + f.Iterate(*r.PrimaryKey, r.Data, func(identifier *model.Identifier, dataRow *model.Record) { + if identifier != nil && dataRow != nil { + data, mergeErr := maps.Merge(maps.Marshal(updatedData), dataRow.DataMap()) + if mergeErr != nil { + err = &er.DataEncodeDecodeError } else { - isValid, err := schema.Validate(data) - if err == nil && isValid { - r.Data[identifier].Data = &data - r.Data[identifier].Metadata = metadata.Use(r.Data[identifier].Metadata).BasicChangeMetadata() + schema, schemaErr := schema.UseSchema(r.Schema) + if schemaErr != nil { + err = schemaErr + } else { + isValid, schemaErr := schema.Validate(data) + if schemaErr == nil && isValid { + r.Data[*identifier].Data = &data + r.Data[*identifier].Metadata = metadata.Use(r.Data[*identifier].Metadata).BasicChangeMetadata() + } + err = schemaErr } - iterError = err } + recordsCount++ } }) - if iterError != nil { - return iterError + if recordsCount == 0 && err == nil { + return &er.RecordDoesNotExistsError } - return nil + + return err } func (r Records) Delete(filters interface{}) *er.WdbError { + var recordsCount int = 0 + var f *filter.Filter + var err *er.WdbError + if filters != nil { - f, err := filter.UseFilter(filters) + f, err = filter.UseFilter(filters) if err != nil { return err } - f.Iterate(*r.PrimaryKey, r.Data, func(identifier model.Identifier, dataRow model.Record) { - delete(r.Data, identifier) + f.Iterate(*r.PrimaryKey, r.Data, func(identifier *model.Identifier, dataRow *model.Record) { + if identifier != nil { + delete(r.Data, *identifier) + recordsCount++ + } }) + + if recordsCount == 0 { + return &er.RecordDoesNotExistsError + } + return nil } + return &er.FilterMissingError } diff --git a/internal/server/handlers/records.go b/internal/server/handlers/records.go index 10d2e9d..405d1c2 100644 --- a/internal/server/handlers/records.go +++ b/internal/server/handlers/records.go @@ -11,6 +11,7 @@ import ( const ( emptyFilter = "" + idKey = "id" ) type queryRequest struct { @@ -63,6 +64,8 @@ func (wh wdbHandlers) ReadRecords(c *fiber.Ctx) error { databaseName := c.Params("database") collectionName := c.Params("collection") + id := c.Params("id") + entities := model.Entities{ Databases: &databaseName, Collections: &collectionName, @@ -73,13 +76,18 @@ func (wh wdbHandlers) ReadRecords(c *fiber.Ctx) error { apiError = error } else { filterKey, filterValue := c.Query("key"), c.Query("value") - if filterKey == emptyFilter || filterValue == emptyFilter { - filter = nil - } else { + if id != emptyFilter { + filter = map[string]interface{}{ + "key": idKey, + "value": id, + } + } else if filterKey != emptyFilter || filterValue != emptyFilter { filter = map[string]interface{}{ "key": filterKey, "value": filterValue, } + } else { + filter = nil } fetchedData, apiError = wh.wdbClient.GetRecords(model.Identifier(databaseName), model.Identifier(collectionName), filter) @@ -151,6 +159,8 @@ func (wh wdbHandlers) DeleteRecords(c *fiber.Ctx) error { databaseName := c.Params("database") collectionName := c.Params("collection") + id := c.Params("id") + entities := model.Entities{ Databases: &databaseName, Collections: &collectionName, @@ -161,13 +171,18 @@ func (wh wdbHandlers) DeleteRecords(c *fiber.Ctx) error { apiError = error } else { filterKey, filterValue := c.Query("key"), c.Query("value") - if filterKey == emptyFilter || filterValue == emptyFilter { - filter = nil - } else { + if id != emptyFilter { + filter = map[string]interface{}{ + "key": idKey, + "value": id, + } + } else if filterKey != emptyFilter || filterValue != emptyFilter { filter = map[string]interface{}{ "key": filterKey, "value": filterValue, } + } else { + filter = nil } apiError = wh.wdbClient.DeleteRecords(model.Identifier(databaseName), model.Identifier(collectionName), filter) @@ -193,6 +208,8 @@ func (wh wdbHandlers) UpdateRecords(c *fiber.Ctx) error { databaseName := c.Params("database") collectionName := c.Params("collection") + id := c.Params("id") + entities := model.Entities{ Databases: &databaseName, Collections: &collectionName, @@ -208,13 +225,18 @@ func (wh wdbHandlers) UpdateRecords(c *fiber.Ctx) error { } filterKey, filterValue := c.Query("key"), c.Query("value") - if filterKey == emptyFilter || filterValue == emptyFilter { - filter = nil - } else { + if id != emptyFilter { + filter = map[string]interface{}{ + "key": idKey, + "value": id, + } + } else if filterKey != emptyFilter || filterValue != emptyFilter { filter = map[string]interface{}{ "key": filterKey, "value": filterValue, } + } else { + filter = nil } apiError = wh.wdbClient.UpdateRecords(model.Identifier(databaseName), model.Identifier(collectionName), incomingUpdatedData, filter) diff --git a/internal/server/routes/routes.go b/internal/server/routes/routes.go index 511b9eb..b3ce61b 100644 --- a/internal/server/routes/routes.go +++ b/internal/server/routes/routes.go @@ -10,13 +10,18 @@ const ( FetchCollection = "/databases/:database/collections/:collection" DeleteCollection = "/databases/:database/collections/:collection" - // Data Routes + // Records Routes AddRecords = "/databases/:database/collections/:collection/records" ReadRecords = "/databases/:database/collections/:collection/records" QueryRecords = "/databases/:database/collections/:collection/records/query" // route for executing jsonpath queries DeleteRecords = "/databases/:database/collections/:collection/records" UpdateRecords = "/databases/:database/collections/:collection/records" + // Record Routes + ReadRecord = "/databases/:database/collections/:collection/records/:id" + DeleteRecord = "/databases/:database/collections/:collection/records/:id" + UpdateRecord = "/databases/:database/collections/:collection/records/:id" + // Role Routes CreateRole = "/roles" ListRoles = "/roles" diff --git a/internal/server/server.go b/internal/server/server.go index f5763d8..7a1256e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -60,13 +60,18 @@ func (ws wdbServer) Start() { api.Get(routes.FetchCollection, ws.handler.FetchCollection) api.Delete(routes.DeleteCollection, ws.handler.DeleteCollection) - // Data Routes + // Records Routes api.Post(routes.AddRecords, ws.handler.AddRecords) api.Get(routes.ReadRecords, ws.handler.ReadRecords) api.Post(routes.QueryRecords, ws.handler.QueryRecords) api.Delete(routes.DeleteRecords, ws.handler.DeleteRecords) api.Patch(routes.UpdateRecords, ws.handler.UpdateRecords) + // Record Routes + api.Get(routes.ReadRecord, ws.handler.ReadRecords) + api.Delete(routes.DeleteRecord, ws.handler.DeleteRecords) + api.Patch(routes.UpdateRecord, ws.handler.UpdateRecords) + // Role Routes api.Post(routes.CreateRole, ws.handler.CreateRole) api.Get(routes.ListRoles, ws.handler.ListRoles) diff --git a/internal/upgrades/steps/step_migrate_data_to_records.go b/internal/upgrades/steps/step_migrate_data_to_records.go index bd68705..20501ee 100644 --- a/internal/upgrades/steps/step_migrate_data_to_records.go +++ b/internal/upgrades/steps/step_migrate_data_to_records.go @@ -20,12 +20,12 @@ func MigrateDataToRecords(c config.Config) error { for dbKey, db := range dbs { if _, ok := db["collections"]; !ok { - return fmt.Errorf("no collections") + return fmt.Errorf("no collections found for database: %s", dbKey) } for cKey, collection := range db["collections"] { if _, ok := collection.(map[string]interface{})["data"]; !ok { - fmt.Println("no data found") + fmt.Println("no data field found for collection: ", cKey) continue } diff --git a/internal/upgrades/steps/steps.go b/internal/upgrades/steps/steps.go index 714b202..5d79d11 100644 --- a/internal/upgrades/steps/steps.go +++ b/internal/upgrades/steps/steps.go @@ -20,6 +20,7 @@ func Run(c config.Config) error { } if constraints.Check(currentVersion) { + fmt.Println("Running Upgrades...") return MigrateDataToRecords(c) } else { fmt.Println("No upgrades to run...") diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index cc185e5..c1e6051 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -2,6 +2,7 @@ package schema import ( "encoding/json" + "strings" "github.com/TanmoySG/wunderDB/model" er "github.com/TanmoySG/wunderDB/pkg/wdb/errors" @@ -42,7 +43,17 @@ func (s Schema) Validate(data interface{}) (bool, *er.WdbError) { return false, er.SchemaValidationFailed.SetMessage(err.Error()) } - return validity.Valid(), nil + validationErrors := []string{} + for _, err := range validity.Errors() { + validationErrors = append(validationErrors, err.String()) + } + + var validationErr *er.WdbError = nil + if !validity.Valid() { + validationErr = er.SchemaValidationFailed.SetMessage(strings.Join(validationErrors, " | ")) + } + + return validity.Valid(), validationErr } // adds default schema fields if not present, eg: additionalProperties [default: false] diff --git a/pkg/wdb/errors/errors.go b/pkg/wdb/errors/errors.go index 500f73e..8a57429 100644 --- a/pkg/wdb/errors/errors.go +++ b/pkg/wdb/errors/errors.go @@ -46,6 +46,13 @@ var ( HttpStatusCode: 404, } + // Record Errors + RecordDoesNotExistsError = WdbError{ + ErrCode: "recordMissing", + ErrMessage: "records do not exist", + HttpStatusCode: 404, + } + // Missing Error FilterMissingError = WdbError{ ErrCode: "filterMissing",