Skip to content

Commit c891ec0

Browse files
committed
Params type
1 parent eea922e commit c891ec0

12 files changed

+6131
-232
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ func thermoMount(ctx context.Context, r *http.Request, s *Socket) (interface{},
6868
// is called with the original request context of the socket, the socket itself containing the current
6969
// state and and params that came from the event. Params contain query string parameters and any
7070
// `live-value-` bindings.
71-
func tempUp(ctx context.Context, s *Socket, params map[string]interface{}) (interface{}, error) {
71+
func tempUp(ctx context.Context, s *Socket, p Params) (interface{}, error) {
7272
model := NewThermoModel(s)
7373
model.C += 0.1
7474
return model, nil
7575
}
7676

7777
// tempDown on the temp down event, decrease the thermostat temperature by .1 C.
78-
func tempDown(ctx context.Context, s *Socket, params map[string]interface{}) (interface{}, error) {
78+
func tempDown(ctx context.Context, s *Socket, p Params) (interface{}, error) {
7979
model := NewThermoModel(s)
8080
model.C -= 0.1
8181
return model, nil
@@ -215,9 +215,9 @@ Clicking on this tag will result in the browser URL being updated, and then an e
215215
trigger the handler's `HandleParams` callback. With the query string being available in the params map of the handler.
216216

217217
```go
218-
h.HandleParams(func(s *live.Socket, p map[string]interface{}) (interface{}, error) {
218+
h.HandleParams(func(s *live.Socket, p live.Params) (interface{}, error) {
219219
...
220-
page := live.ParamInt(p, "page")
220+
page := p.Int("page")
221221
...
222222
})
223223
```

event.go

+17-91
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package live
22

33
import (
44
"context"
5-
"net/http"
6-
"strconv"
5+
"encoding/json"
76
)
87

98
// EventHandler a function to handle events, returns the data that should
109
// be set to the socket after handling.
11-
type EventHandler func(context.Context, *Socket, map[string]interface{}) (interface{}, error)
10+
type EventHandler func(context.Context, *Socket, Params) (interface{}, error)
11+
12+
// EventConfig configures an event.
13+
type EventConfig func(e *Event) error
1214

1315
const (
1416
// EventError indicates an error has occured.
@@ -31,103 +33,27 @@ const (
3133
// Event messages that are sent and received by the
3234
// socket.
3335
type Event struct {
34-
T string `json:"t"`
35-
ID int `json:"i,omitempty"`
36-
Data interface{} `json:"d,omitempty"`
36+
T string `json:"t"`
37+
ID int `json:"i,omitempty"`
38+
Data json.RawMessage `json:"d,omitempty"`
3739
}
3840

3941
// Params extract params from inbound message.
40-
func (e Event) Params() (map[string]interface{}, error) {
42+
func (e Event) Params() (Params, error) {
4143
if e.Data == nil {
42-
return map[string]interface{}{}, nil
44+
return Params{}, nil
4345
}
44-
p, ok := e.Data.(map[string]interface{})
45-
if !ok {
46+
var p Params
47+
if err := json.Unmarshal(e.Data, &p); err != nil {
4648
return nil, ErrMessageMalformed
4749
}
4850
return p, nil
4951
}
5052

51-
// ParamString helper to return a string from the params.
52-
func ParamString(params map[string]interface{}, key string) string {
53-
v, ok := params[key]
54-
if !ok {
55-
return ""
56-
}
57-
out, ok := v.(string)
58-
if !ok {
59-
return ""
60-
}
61-
return out
62-
}
63-
64-
// ParamCheckbox helper to return a boolean from params referring to
65-
// a checkbox input.
66-
func ParamCheckbox(params map[string]interface{}, name string) bool {
67-
v, ok := params[name]
68-
if !ok {
69-
return false
70-
}
71-
out, ok := v.(string)
72-
if !ok {
73-
return false
74-
}
75-
if out == "on" {
76-
return true
77-
}
78-
return false
79-
}
80-
81-
// ParamInt helper to return an int from the params.
82-
func ParamInt(params map[string]interface{}, key string) int {
83-
v, ok := params[key]
84-
if !ok {
85-
return 0
86-
}
87-
switch out := v.(type) {
88-
case int:
89-
return out
90-
case string:
91-
i, err := strconv.Atoi(out)
92-
if err != nil {
93-
return 0
94-
}
95-
return i
96-
}
97-
return 0
98-
}
99-
100-
// ParamFloat32 helper to return a float32 from the params.
101-
func ParamFloat32(params map[string]interface{}, key string) float32 {
102-
v, ok := params[key]
103-
if !ok {
104-
return 0.0
105-
}
106-
switch out := v.(type) {
107-
case float32:
108-
return out
109-
case float64:
110-
return float32(out)
111-
case string:
112-
f, err := strconv.ParseFloat(out, 32)
113-
if err != nil {
114-
return 0.0
115-
}
116-
return float32(f)
117-
}
118-
return 0.0
119-
}
120-
121-
// ParamsFromRequest given an *http.Request extract the params.
122-
func ParamsFromRequest(r *http.Request) map[string]interface{} {
123-
out := map[string]interface{}{}
124-
values := r.URL.Query()
125-
for k, v := range values {
126-
if len(v) == 1 {
127-
out[k] = v[0]
128-
} else {
129-
out[k] = v
130-
}
53+
// WithID sets an ID on an event.
54+
func WithID(ID int) EventConfig {
55+
return func(e *Event) error {
56+
e.ID = ID
57+
return nil
13158
}
132-
return out
13359
}

event_test.go

+1-102
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package live
22

33
import (
4-
"net/http"
5-
"net/url"
64
"testing"
75
)
86

@@ -16,108 +14,9 @@ func TestEventParams(t *testing.T) {
1614
t.Fatal("expected zero length map, got", p)
1715
}
1816

19-
e.Data = "wrong"
17+
e.Data = []byte("wrong")
2018
p, err = e.Params()
2119
if err != ErrMessageMalformed {
2220
t.Error("expected ErrMessageMalformed, got", err)
2321
}
2422
}
25-
26-
func TestParamString(t *testing.T) {
27-
p := map[string]interface{}{"test": "output"}
28-
out := ParamString(p, "test")
29-
if out != "output" {
30-
t.Error("unexpected output of ParamString", out)
31-
}
32-
33-
empty := ParamString(p, "nokey")
34-
if empty != "" {
35-
t.Error("unexpected output of ParamString", empty)
36-
}
37-
}
38-
39-
func TestParamCheckbox(t *testing.T) {
40-
p := map[string]interface{}{"test": "on"}
41-
state := ParamCheckbox(p, "test")
42-
if state != true {
43-
t.Error("unexpected output of ParamCheckbox", state)
44-
}
45-
p["test"] = "noton"
46-
state = ParamCheckbox(p, "test")
47-
if state != false {
48-
t.Error("unexpected output of ParamCheckbox", state)
49-
}
50-
state = ParamCheckbox(p, "nottest")
51-
if state != false {
52-
t.Error("unexpected output of ParamCheckbox", state)
53-
}
54-
}
55-
56-
func TestParamInt(t *testing.T) {
57-
var out int
58-
59-
out = ParamInt(map[string]interface{}{"test": 1}, "test")
60-
if out != 1 {
61-
t.Error("unexpected output of ParamInt", out)
62-
}
63-
out = ParamInt(map[string]interface{}{"test": "1"}, "test")
64-
if out != 1 {
65-
t.Error("unexpected output of ParamInt", out)
66-
}
67-
out = ParamInt(map[string]interface{}{"test": "aaa"}, "test")
68-
if out != 0 {
69-
t.Error("unexpected output of ParamInt", out)
70-
}
71-
out = ParamInt(map[string]interface{}{"test": 1}, "nottest")
72-
if out != 0 {
73-
t.Error("unexpected output of ParamInt", out)
74-
}
75-
}
76-
77-
func TestParamFloat32(t *testing.T) {
78-
var out float32
79-
80-
out = ParamFloat32(map[string]interface{}{"test": 1.0}, "test")
81-
if out != 1.0 {
82-
t.Error("unexpected output of ParamFloat32", out)
83-
}
84-
out = ParamFloat32(map[string]interface{}{"test": "1.0"}, "test")
85-
if out != 1.0 {
86-
t.Error("unexpected output of ParamFloat32", out)
87-
}
88-
out = ParamFloat32(map[string]interface{}{"test": "aaa"}, "test")
89-
if out != 0.0 {
90-
t.Error("unexpected output of ParamFloat32", out)
91-
}
92-
out = ParamFloat32(map[string]interface{}{"test": 1.0}, "nottest")
93-
if out != 0.0 {
94-
t.Error("unexpected output of ParamFloat32", out)
95-
}
96-
}
97-
98-
func TestParamsFromRequest(t *testing.T) {
99-
var err error
100-
r := &http.Request{}
101-
r.URL, err = url.Parse("http://example.com?one=1&two=2&three=3&three=4")
102-
if err != nil {
103-
t.Fatal(err)
104-
}
105-
params := ParamsFromRequest(r)
106-
var out int
107-
out = ParamInt(params, "one")
108-
if out != 1 {
109-
t.Error("did not get expected params", params)
110-
}
111-
out = ParamInt(params, "two")
112-
if out != 2 {
113-
t.Error("did not get expected params", params)
114-
}
115-
116-
sliceout, ok := params["three"].([]string)
117-
if !ok {
118-
t.Error("did not get expected params", params)
119-
}
120-
if len(sliceout) != 2 {
121-
t.Error("did not get expected params", params)
122-
}
123-
}

example_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ func thermoMount(ctx context.Context, r *http.Request, s *Socket) (interface{},
3636
// is called with the original request context of the socket, the socket itself containing the current
3737
// state and and params that came from the event. Params contain query string parameters and any
3838
// `live-value-` bindings.
39-
func tempUp(ctx context.Context, s *Socket, params map[string]interface{}) (interface{}, error) {
39+
func tempUp(ctx context.Context, s *Socket, p Params) (interface{}, error) {
4040
model := NewThermoModel(s)
4141
model.C += 0.1
4242
return model, nil
4343
}
4444

4545
// tempDown on the temp down event, decrease the thermostat temperature by .1 C.
46-
func tempDown(ctx context.Context, s *Socket, params map[string]interface{}) (interface{}, error) {
46+
func tempDown(ctx context.Context, s *Socket, p Params) (interface{}, error) {
4747
model := NewThermoModel(s)
4848
model.C -= 0.1
4949
return model, nil

handler.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
142142
}
143143

144144
// Broadcast send a message to all sockets connected to this handler.
145-
func (h *Handler) Broadcast(msg Event) {
145+
func (h *Handler) Broadcast(event string, data interface{}) error {
146+
payload, err := json.Marshal(data)
147+
if err != nil {
148+
return fmt.Errorf("could not encode data for broadcast: %w", err)
149+
}
150+
e := Event{T: event, Data: payload}
146151
ctx := context.Background()
147152
h.broadcastLimiter.Wait(ctx)
148-
h.broadcast(ctx, h, msg)
153+
h.broadcast(ctx, h, e)
154+
return nil
149155
}
150156

151157
// HandleEvent handles an event that comes from the client. For example a click
@@ -304,7 +310,9 @@ func (h *Handler) _serveWS(r *http.Request, session Session, c *websocket.Conn)
304310
if err := sock.render(ctx, h); err != nil {
305311
internalErrors <- fmt.Errorf("socket handle error: %w", err)
306312
}
307-
sock.Send(Event{T: EventAck, ID: m.ID})
313+
if err := sock.Send(EventAck, nil, WithID(m.ID)); err != nil {
314+
internalErrors <- fmt.Errorf("socket send error: %w", err)
315+
}
308316
case websocket.MessageBinary:
309317
log.Println("binary messages unhandled")
310318
}
@@ -339,12 +347,16 @@ func (h *Handler) _serveWS(r *http.Request, session Session, c *websocket.Conn)
339347
return fmt.Errorf("writing to socket error: %w", err)
340348
}
341349
case ee := <-eventErrors:
342-
if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: ee}); err != nil {
350+
d, err := json.Marshal(ee)
351+
if err != nil {
352+
return fmt.Errorf("writing to socket error: %w", err)
353+
}
354+
if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: d}); err != nil {
343355
return fmt.Errorf("writing to socket error: %w", err)
344356
}
345357
case err := <-internalErrors:
346358
if err != nil {
347-
if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: err.Error()}); err != nil {
359+
if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: []byte(err.Error())}); err != nil {
348360
return fmt.Errorf("writing to socket error: %w", err)
349361
}
350362
}

internal/embed/blob.go

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

page/component.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type RenderHandler func(w io.Writer, c *Component) error
2222

2323
// EventHandler for a component, only needs the params as the event is scoped to both the socket and then component
2424
// iteslef. Returns any component state that needs updating.
25-
type EventHandler func(ctx context.Context, params map[string]interface{}) (interface{}, error)
25+
type EventHandler func(ctx context.Context, p live.Params) (interface{}, error)
2626

2727
// ComponentConstructor a func for creating a new component.
2828
type ComponentConstructor func(ctx context.Context, h *live.Handler, r *http.Request, s *live.Socket) (*Component, error)
@@ -89,14 +89,13 @@ func Init(ctx context.Context, construct func() (*Component, error)) (*Component
8989

9090
// Self sends an event scoped not only to this socket, but to this specific component instance. Or any
9191
// components sharing the same ID.
92-
func (c *Component) Self(ctx context.Context, s *live.Socket, event live.Event) {
93-
event.T = c.Event(event.T)
94-
s.Self(ctx, event)
92+
func (c *Component) Self(ctx context.Context, s *live.Socket, event string, data interface{}) error {
93+
return s.Self(ctx, c.Event(event), data)
9594
}
9695

9796
// HandleSelf handles scoped incoming events send by a components Self function.
9897
func (c *Component) HandleSelf(event string, handler EventHandler) {
99-
c.Handler.HandleSelf(c.Event(event), func(ctx context.Context, s *live.Socket, p map[string]interface{}) (interface{}, error) {
98+
c.Handler.HandleSelf(c.Event(event), func(ctx context.Context, s *live.Socket, p live.Params) (interface{}, error) {
10099
state, err := handler(ctx, p)
101100
if err != nil {
102101
return s.Assigns(), err
@@ -108,7 +107,7 @@ func (c *Component) HandleSelf(event string, handler EventHandler) {
108107

109108
// HandleEvent handles a component event sent from a connected socket.
110109
func (c *Component) HandleEvent(event string, handler EventHandler) {
111-
c.Handler.HandleEvent(c.Event(event), func(ctx context.Context, s *live.Socket, p map[string]interface{}) (interface{}, error) {
110+
c.Handler.HandleEvent(c.Event(event), func(ctx context.Context, s *live.Socket, p live.Params) (interface{}, error) {
112111
state, err := handler(ctx, p)
113112
if err != nil {
114113
return s.Assigns(), err
@@ -120,7 +119,7 @@ func (c *Component) HandleEvent(event string, handler EventHandler) {
120119

121120
// HandleParams handles parameter changes. Caution these handlers are not scoped to a specific component.
122121
func (c *Component) HandleParams(handler EventHandler) {
123-
c.Handler.HandleParams(func(ctx context.Context, s *live.Socket, p map[string]interface{}) (interface{}, error) {
122+
c.Handler.HandleParams(func(ctx context.Context, s *live.Socket, p live.Params) (interface{}, error) {
124123
state, err := handler(ctx, p)
125124
if err != nil {
126125
return s.Assigns(), err

0 commit comments

Comments
 (0)