Skip to content

Commit

Permalink
add RouteContext.State to mark data only available for event receiver…
Browse files Browse the repository at this point in the history
…s without a bounded template
  • Loading branch information
adnaan committed Jun 9, 2023
1 parent 402e6ec commit 53c55e1
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 50 deletions.
13 changes: 7 additions & 6 deletions pubsub/pubsub.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import (
// code modeled after https://github.com/purposeinplay/go-commons/blob/v0.6.2/pubsub/inmem/pubsub.go

type Event struct {
ID *string `json:"id"`
State eventstate.Type `json:"state"`
Target *string `json:"target"`
Detail any `json:"detail"`
SessionID *string `json:"session_id"`
ElementKey *string `json:"element_key"`
ID *string `json:"id"`
State eventstate.Type `json:"state"`
Target *string `json:"target"`
Detail any `json:"detail"`
StateDetail any `json:"state_detail"`
SessionID *string `json:"session_id"`
ElementKey *string `json:"element_key"`
}

// Subscription is a subscription to a channel.
Expand Down
2 changes: 1 addition & 1 deletion render.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func buildDOMEventFromTemplate(ctx RouteContext, pubsubEvent pubsub.Event, event
Type: eventType,
Key: pubsubEvent.ElementKey,
Target: targetOrClassName(pubsubEvent.Target, getClassName(*eventType)),
Detail: pubsubEvent.Detail,
Detail: pubsubEvent.StateDetail,
}
}
eventType := fir(eventIDWithState, templateName)
Expand Down
52 changes: 42 additions & 10 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,6 @@ func OnEvent(name string, onEventFunc OnEventFunc) RouteOption {
}
}

type routeData map[string]any

func (r *routeData) Error() string {
b, _ := json.Marshal(r)
return string(b)
}

type routeRenderer func(data routeData) error
type eventPublisher func(event pubsub.Event) error
type routeOpt struct {
Expand Down Expand Up @@ -442,16 +435,37 @@ func handleOnEventResult(err error, ctx RouteContext, publish eventPublisher) {
})
return
case *routeData:
data := *errVal
publish(pubsub.Event{
ID: &ctx.event.ID,
State: eventstate.OK,
Target: &target,
ElementKey: ctx.event.ElementKey,
Detail: data,
Detail: *errVal,
SessionID: ctx.event.SessionID,
})
return

case *routeDataWithState:
publish(pubsub.Event{
ID: &ctx.event.ID,
State: eventstate.OK,
Target: &target,
ElementKey: ctx.event.ElementKey,
Detail: *errVal.routeData,
StateDetail: *errVal.stateData,
SessionID: ctx.event.SessionID,
})
return
case *stateData:
publish(pubsub.Event{
ID: &ctx.event.ID,
State: eventstate.OK,
Target: &target,
ElementKey: ctx.event.ElementKey,
StateDetail: *errVal,
SessionID: ctx.event.SessionID,
})
return
default:
errs := map[string]any{
ctx.event.ID: firErrors.User(err).Error(),
Expand All @@ -476,7 +490,7 @@ func handlePostFormResult(err error, ctx RouteContext) {
}

switch err.(type) {
case *routeData:
case *routeData, *stateData, *routeDataWithState:
http.Redirect(ctx.response, ctx.request, ctx.request.URL.Path, http.StatusFound)
default:
handleOnLoadResult(ctx.route.onLoad(ctx), err, ctx)
Expand Down Expand Up @@ -522,6 +536,24 @@ func handleOnLoadResult(err, onFormErr error, ctx RouteContext) {
onLoadData["fir"] = newRouteDOMContext(ctx, errs)
renderRoute(ctx, false)(onLoadData)

case *routeDataWithState:
onLoadData := *errVal.routeData
errs := make(map[string]any)
if onFormErr != nil {
fieldErrorsVal, ok := onFormErr.(*firErrors.Fields)
if !ok {
errs = map[string]any{
ctx.event.ID: onFormErr.Error(),
}
} else {
errs = map[string]any{
ctx.event.ID: fieldErrorsVal.Map(),
}
}
}
onLoadData["fir"] = newRouteDOMContext(ctx, errs)
renderRoute(ctx, false)(onLoadData)

case firErrors.Status:
errs := make(map[string]any)
if onFormErr != nil {
Expand Down
47 changes: 14 additions & 33 deletions route_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,19 @@ func (c RouteContext) Redirect(url string, status int) error {

// KV is a wrapper for ctx.Data(map[string]any{key: data})
func (c RouteContext) KV(key string, data any) error {
return c.Data(map[string]any{key: data})
return buildData(false, map[string]any{key: data})
}

// KV is a wrapper for ctx.State(map[string]any{key: data})
func (c RouteContext) StateKV(key string, data any) error {
return buildData(true, map[string]any{key: data})
}

// State data is only passed to event receiver without a bound template
// it can be acccessed in the event receiver via $event.detail
// e.g. @fir:myevent:ok="console.log('$event.detail.mykey')"
func (c RouteContext) State(dataset ...any) error {
return buildData(true, dataset...)
}

// Data sets the data to be hydrated into the route's template or an event's associated template/block action
Expand All @@ -170,38 +182,7 @@ func (c RouteContext) KV(key string, data any) error {
// The function will return nil if no data is passed
// The function accepts variadic arguments so that you can pass multiple structs or maps which will be merged
func (c RouteContext) Data(dataset ...any) error {
if len(dataset) == 0 {
return nil
}
m := routeData{}
for _, data := range dataset {
val := reflect.ValueOf(data)
if val.Kind() == reflect.Ptr {
el := val.Elem() // dereference the pointer
if el.Kind() == reflect.Struct {
for k, v := range structs.Map(data) {
m[k] = v
}
}
} else if val.Kind() == reflect.Struct {
for k, v := range structs.Map(data) {
m[k] = v
}
} else if val.Kind() == reflect.Map {
ms, ok := data.(map[string]any)
if !ok {
return errors.New("data must be a map[string]any , struct or pointer to a struct")
}

for k, v := range ms {
m[k] = v
}
} else {
return errors.New("data must be a map[string]any , struct or pointer to a struct")
}
}

return &m
return buildData(false, dataset...)
}

// FieldError sets the error message for the given field and can be looked up by {{.fir.Error "myevent.field"}}
Expand Down
100 changes: 100 additions & 0 deletions route_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fir

import (
"encoding/json"
"errors"
"fmt"
"reflect"

"github.com/fatih/structs"
)

type routeData map[string]any

func (r *routeData) Error() string {
b, _ := json.Marshal(r)
return string(b)
}

type stateData map[string]any

func (r *stateData) Error() string {
b, _ := json.Marshal(r)
return string(b)
}

type routeDataWithState struct {
routeData *routeData
stateData *stateData
}

func (r *routeDataWithState) Error() string {
b1, _ := json.Marshal(r.routeData)
b2, _ := json.Marshal(r.stateData)
return fmt.Sprintf("routeData: %s\n stateData: %s", string(b1), string(b2))
}

func buildData(stateOnly bool, dataset ...any) error {
if len(dataset) == 0 {
return nil
}

m := make(map[string]any)
hasState := false
state := make(stateData)

for _, data := range dataset {
if data == nil {
continue
}
if sv, ok := data.(*stateData); ok {
hasState = true
for k, v := range *sv {
state[k] = v
m[k] = v
}
}
val := reflect.ValueOf(data)

if val.Kind() == reflect.Ptr {
el := val.Elem() // dereference the pointer
if el.Kind() == reflect.Struct {
for k, v := range structs.Map(data) {
m[k] = v
}
}
} else if val.Kind() == reflect.Struct {
for k, v := range structs.Map(data) {
m[k] = v
}
} else if val.Kind() == reflect.Map {
ms, ok := data.(map[string]any)
if !ok {
return errors.New("data must be a map[string]any , struct or pointer to a struct")
}

for k, v := range ms {
m[k] = v
}
} else {
return errors.New("data must be a map[string]any , struct or pointer to a struct")
}
}

if stateOnly {
t := stateData(m)
return &t
}

if hasState {
r := routeData(m)
t := routeDataWithState{
routeData: &r,
stateData: &state,
}
return &t
}

r := routeData(m)
return &r
}
64 changes: 64 additions & 0 deletions route_data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fir

import (
"reflect"
"testing"
)

type TestData struct {
Name string
Age int
}

func TestBuildData(t *testing.T) {
// Test case 1: No dataset provided
err := buildData(false)
if err != nil {
t.Errorf("Expected nil error, got: %v", err)
}

// Test case 2: Only routeData provided
data := map[string]any{"key": "value"}
err = buildData(false, data)
if err == nil {
t.Errorf("Expected error, got: %v", err)
}
r, ok := err.(*routeData)
if !ok {
t.Errorf("Expected error type *routeData, got: %v", reflect.TypeOf(err))
}
if !reflect.DeepEqual(*r, routeData{"key": "value"}) {
t.Errorf("Expected error value %v, got: %v", data, *r)
}

// Test case 2: Only stateData provided
err = buildData(true, data)
if err == nil {
t.Errorf("Expected error, got: %v", err)
}
s, ok := err.(*stateData)
if !ok {
t.Errorf("Expected error type *stateData, got: %v", reflect.TypeOf(err))
}
if !reflect.DeepEqual(*s, stateData{"key": "value"}) {
t.Errorf("Expected error value %v, got: %v", data, *s)
}

// Test case 3: Both routeData and stateData provided
err = buildData(false, data, buildData(true, map[string]any{"key1": "value1"}))
if err == nil {
t.Errorf("Expected error, got: %v", err)
}
rs, ok := err.(*routeDataWithState)
if !ok {
t.Errorf("Expected error type *routeDataWithState, got: %v", reflect.TypeOf(err))
}
expectedRouteData := routeData{"key": "value", "key1": "value1"}
if !reflect.DeepEqual(*rs.routeData, expectedRouteData) {
t.Errorf("Expected error value %v, got: %v", expectedRouteData, *rs.routeData)
}
if !reflect.DeepEqual(*rs.stateData, stateData{"key1": "value1"}) {
t.Errorf("Expected error value %v, got: %v", data, *rs.stateData)
}

}

0 comments on commit 53c55e1

Please sign in to comment.