Skip to content

Commit

Permalink
feature: WEOS-1135 Improved how we use schemas to create the response
Browse files Browse the repository at this point in the history
* Switched to getting the events in the controller by using the event repository
* Added method to ContentEntity that returns the entity as an interface map whose keys are what was set in the OpenAPI spec
* Added constructor for ContentEnty to create with schema and events
* Added method to Content entity to convert the name of property in dynamic struct back that what was set in the OpenAPI spec
  • Loading branch information
akeemphilbert committed Jan 29, 2022
1 parent e42cd78 commit 349ddb8
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 20 deletions.
12 changes: 5 additions & 7 deletions controllers/rest/controller_standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,18 +413,16 @@ func (c *StandardControllers) View(app model.Service, spec *openapi3.Swagger, pa
if entityID != "" {
//get the entity using the sequence no.
if seqInt != 0 {
r, er := model.GetContentBySequenceNumber(app.EventRepository(), entityID, int64(seqInt))
//get the events up to the sequence
events, err := app.EventRepository().GetByAggregateAndSequenceRange(entityID, 0, int64(seqInt))
//create content entity
r, er := new(model.ContentEntity).FromSchemaWithEvents(ctxt.Request().Context(), contentTypeSchema.Value, events)
err = er
if r.SequenceNo == 0 {
return NewControllerError("No entity found", err, http.StatusNotFound)
}
if r != nil && r.ID != "" { //get the map from the entity
if r.Property != nil {
result = r.Property.(map[string]interface{})
} else {
result = make(map[string]interface{})
}

result = r.ToMap()
}
result["weos_id"] = r.ID
result["sequence_no"] = r.SequenceNo
Expand Down
17 changes: 13 additions & 4 deletions controllers/rest/controller_standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -838,8 +839,16 @@ func TestStandardControllers_View(t *testing.T) {
e.GET("/blogs/:"+paramName, controller, mw)
e.ServeHTTP(resp, req)

response := resp.Result()
defer response.Body.Close()
response, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("invalid response '%s'", err)
}
defer resp.Body.Reset()

//check that properties of the scehma are in the response even if it was not set in the event
if !strings.Contains(string(response), "title") {
t.Errorf("expected the response to have '%s' based on the schema, got '%s'", "title", string(response))
}

//confirm the entity is retrieved to get entity id
if len(projection.GetByKeyCalls()) != 1 {
Expand All @@ -850,8 +859,8 @@ func TestStandardControllers_View(t *testing.T) {
t.Errorf("expected the event repository to be called %d time, called %d times", 1, len(eventRepository.GetByAggregateAndSequenceRangeCalls()))
}

if response.StatusCode != 200 {
t.Errorf("expected response code to be %d, got %d", 200, response.StatusCode)
if resp.Code != 200 {
t.Errorf("expected response code to be %d, got %d", 200, resp.Code)
}
})
t.Run("view with invalid sequence no", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion controllers/rest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func GetContentBySequenceNumber(eventRepository model.EventRepository, id string
if err != nil {
return nil, err
}
err = entity.ApplyChanges(events)
err = entity.ApplyEvents(events)
return entity, err
}

Expand Down
46 changes: 38 additions & 8 deletions model/content_entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type ContentEntity struct {
AggregateRoot
Schema *openapi3.Schema
Property interface{}
reader ds.Reader
}

//IsValid checks if the property is valid using the IsNull function
Expand Down Expand Up @@ -128,6 +129,7 @@ func (w *ContentEntity) FromSchema(ctx context.Context, ref *openapi3.Schema) (*
}
}
w.Property = instance.Build().New()
w.reader = ds.NewReader(w.Property)
return w, nil

}
Expand All @@ -150,7 +152,7 @@ func (w *ContentEntity) FromSchemaWithValues(ctx context.Context, schema *openap
}
event := NewEntityEvent("create", w, w.ID, payload)
w.NewChange(event)
return w, w.ApplyChanges([]*Event{event})
return w, w.ApplyEvents([]*Event{event})
}

func (w *ContentEntity) Update(ctx context.Context, existingPayload json.RawMessage, updatedPayload json.RawMessage) (*ContentEntity, error) {
Expand All @@ -171,7 +173,7 @@ func (w *ContentEntity) Update(ctx context.Context, existingPayload json.RawMess

event := NewEntityEvent("update", w, w.ID, updatedPayload)
w.NewChange(event)
return w, w.ApplyChanges([]*Event{event})
return w, w.ApplyEvents([]*Event{event})
}

//GetString returns the string property value stored of a given the property name
Expand Down Expand Up @@ -270,18 +272,21 @@ func (w *ContentEntity) GetTime(name string) time.Time {
return *reader.GetField(name).PointerTime()
}

func GetContentBySequenceNumber(eventRepository EventRepository, id string, sequence_no int64) (*ContentEntity, error) {
entity := &ContentEntity{}
events, err := eventRepository.GetByAggregateAndSequenceRange(id, 0, sequence_no)
//FromSchemaWithEvents create content entity using schema and events
func (w *ContentEntity) FromSchemaWithEvents(ctx context.Context, ref *openapi3.Schema, changes []*Event) (*ContentEntity, error) {
entity, err := w.FromSchema(ctx, ref)
if err != nil {
return nil, err
}
err = entity.ApplyEvents(changes)
if err != nil {
return nil, err
}
err = entity.ApplyChanges(events)
return entity, err
}

//ApplyChanges apply the new changes from payload to the entity
func (w *ContentEntity) ApplyChanges(changes []*Event) error {
//ApplyEvents apply the new changes from payload to the entity
func (w *ContentEntity) ApplyEvents(changes []*Event) error {
for _, change := range changes {
w.SequenceNo = change.Meta.SequenceNo
w.ID = change.Meta.EntityID
Expand Down Expand Up @@ -311,3 +316,28 @@ func (w *ContentEntity) ApplyChanges(changes []*Event) error {
}
return nil
}

//ToMap return entity has a map
func (w *ContentEntity) ToMap() map[string]interface{} {
result := make(map[string]interface{})
//get all fields and return the map
fields := w.reader.GetAllFields()
for _, field := range fields {
//check if the lowercase version of the field is the same as the schema and use the scehma version instead
result[w.GetOriginalFieldName(field.Name())] = field.Interface()
}
return result
}

//GetOriginalFieldName the original name of the field as defined in the schema (the field is Title cased when converted to struct)
func (w *ContentEntity) GetOriginalFieldName(structName string) string {
if w.Schema != nil {
for key, _ := range w.Schema.Properties {
if strings.ToLower(key) == strings.ToLower(structName) {
return key
}
}
}

return ""
}
128 changes: 128 additions & 0 deletions model/content_entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,131 @@ func TestContentEntity_Update(t *testing.T) {
t.Errorf("expected the updated description to be '%s', got '%s'", "Updated Description", existingEntity.GetString("Description"))
}
}

func TestContentEntity_FromSchemaWithEvents(t *testing.T) {
//load open api spec
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog.yaml")
if err != nil {
t.Fatalf("unexpected error occured '%s'", err)
}
var contentType string
var contentTypeSchema *openapi3.SchemaRef
contentType = "Blog"
contentTypeSchema = swagger.Components.Schemas[contentType]
ctx := context.Background()
ctx = context.WithValue(ctx, weosContext.CONTENT_TYPE, &weosContext.ContentType{
Name: contentType,
Schema: contentTypeSchema.Value,
})
ctx = context.WithValue(ctx, weosContext.USER_ID, "123")

mockEvent1 := &model.Event{
ID: "1234sd",
Type: "create",
Payload: nil,
Meta: model.EventMeta{
EntityID: "1234sd",
EntityType: "Blog",
SequenceNo: 1,
User: "",
Module: "",
RootID: "",
Group: "",
Created: "",
},
Version: 0,
}
mockEvent2 := &model.Event{
ID: "1234sd",
Type: "update",
Payload: nil,
Meta: model.EventMeta{
EntityID: "1234sd",
EntityType: "Blog",
SequenceNo: 2,
User: "",
Module: "",
RootID: "",
Group: "",
Created: "",
},
Version: 0,
}
events := []*model.Event{mockEvent1, mockEvent2}

entity, err := new(model.ContentEntity).FromSchemaWithEvents(ctx, swagger.Components.Schemas["Blog"].Value, events)
if err != nil {
t.Fatalf("unexpected error instantiating content entity '%s'", err)
}

if entity.Property == nil {
t.Fatal("expected item to be returned")
}

if entity.GetString("Title") != "" {
t.Errorf("expected there to be a field '%s' with value '%s' got '%s'", "Title", " ", entity.GetString("Title"))
}
}

func TestContentEntity_ToMap(t *testing.T) {
//load open api spec
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog.yaml")
if err != nil {
t.Fatalf("unexpected error occured '%s'", err)
}
var contentType string
var contentTypeSchema *openapi3.SchemaRef
contentType = "Blog"
contentTypeSchema = swagger.Components.Schemas[contentType]
ctx := context.Background()
ctx = context.WithValue(ctx, weosContext.CONTENT_TYPE, &weosContext.ContentType{
Name: contentType,
Schema: contentTypeSchema.Value,
})
ctx = context.WithValue(ctx, weosContext.USER_ID, "123")

entity, err := new(model.ContentEntity).FromSchema(ctx, swagger.Components.Schemas["Blog"].Value)
if err != nil {
t.Fatalf("unexpected error instantiating content entity '%s'", err)
}

if entity.Property == nil {
t.Fatal("expected item to be returned")
}

result := entity.ToMap()
if err != nil {
t.Fatalf("unexpected error getting map '%s'", err)
}

if _, ok := result["title"]; !ok {
t.Errorf("expected '%s' to be in map", "title")
}
}

func TestContentEntity_GetOriginalFieldName(t *testing.T) {
//load open api spec
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog.yaml")
if err != nil {
t.Fatalf("unexpected error occured '%s'", err)
}
var contentType string
var contentTypeSchema *openapi3.SchemaRef
contentType = "Blog"
contentTypeSchema = swagger.Components.Schemas[contentType]
ctx := context.Background()
ctx = context.WithValue(ctx, weosContext.CONTENT_TYPE, &weosContext.ContentType{
Name: contentType,
Schema: contentTypeSchema.Value,
})
ctx = context.WithValue(ctx, weosContext.USER_ID, "123")

entity, err := new(model.ContentEntity).FromSchema(ctx, swagger.Components.Schemas["Blog"].Value)
if err != nil {
t.Fatalf("unexpected error instantiating content entity '%s'", err)
}
originalName := entity.GetOriginalFieldName("Title")
if originalName != "title" {
t.Errorf("expected the original field name for '%s' to be '%s', got '%s'", "Title", "title", originalName)
}
}

0 comments on commit 349ddb8

Please sign in to comment.