Skip to content

Commit db54047

Browse files
committed
Fix session memory store
1 parent 42499af commit db54047

File tree

10 files changed

+148
-111
lines changed

10 files changed

+148
-111
lines changed

src/WebRow/HTTP.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module WebRow.HTTP
55
, module Response
66
) where
77

8-
import WebRow.HTTP.Cookies (Cookies, CookieStore, RequestCookies, ResponseCookies) as Cookies
8+
import WebRow.HTTP.Cookies (Cookies, CookieStore(..), RequestCookies, ResponseCookies) as Cookies
99
import WebRow.HTTP.Response.Except (badGateway, badRequest, badRequest', badRequest'', forbidden, HTTPExcept, HTTPException(..), internalServerError, methodNotAllowed, methodNotAllowed', notFound, notImplemented, redirect, serviceUnavailable, unauthorized) as Response.Except
1010
import WebRow.HTTP.Request (Request, headers, method, body, fullPath) as Request
1111
import WebRow.HTTP.Response (HTTPResponse(..), setHeader, SetHeader) as Response

src/WebRow/HTTP/Cookies.purs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module WebRow.HTTP.Cookies
99
, lookupJson
1010
, lookupJson'
1111
, run
12+
, runOnStore
1213
, set
1314
, setJson
1415
) where
@@ -26,7 +27,7 @@ import Type.Prelude (SProxy(..))
2627
import Type.Row (type (+))
2728
import WebRow.Contrib.Data.JSDate (epoch)
2829
import WebRow.Crypto (Crypto, secret)
29-
import WebRow.HTTP.Cookies.CookieStore (CookieStore) as Exports
30+
import WebRow.HTTP.Cookies.CookieStore (CookieStore(..)) as Exports
3031
import WebRow.HTTP.Cookies.CookieStore (CookieStore, cookieStore, toSetCookieHeaders)
3132
import WebRow.HTTP.Cookies.CookieStore (lookup, lookup', lookupJson, lookupJson', set, setJson) as CookieStore
3233
import WebRow.HTTP.Cookies.Types (Attributes, Name, SetValue, Value, Values, attributes)

src/WebRow/KeyValueStore/InMemory.purs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module WebRow.KeyValueStore.InMemory where
22

33
import Prelude
4+
45
import Data.Map (Map)
56
import Data.Map (delete, insert, lookup) as Map
67
import Effect (Effect)
@@ -24,7 +25,9 @@ forRef ref =
2425

2526
delete k = (void $ Ref.modify (Map.delete k) ref) *> pure true
2627

27-
get k = Ref.read ref >>= (Map.lookup k >>> pure)
28+
get k = do
29+
m ← Ref.read ref
30+
pure $ Map.lookup k m
2831

2932
put k v = do
3033
void $ Ref.modify (Map.insert k v) ref

src/WebRow/Session.purs

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
module WebRow.Session where
22

33
import Prelude
4+
45
import Data.Argonaut (Json)
56
import Data.Either (hush)
6-
import Data.Lazy (Lazy, defer)
77
import Data.Lazy (force) as Lazy
88
import Data.Map (Map)
99
import Data.Maybe (fromMaybe)
1010
import Data.Validation.Semigroup (toEither)
1111
import Effect (Effect)
1212
import Effect.Ref (Ref)
13-
import Effect.Ref (new, read, write) as Ref
1413
import HTTPure (empty) as Headers
1514
import Polyform.Validator.Dual.Pure (Dual, runSerializer, runValidator) as Pure
16-
import Run (FProxy, Run, SProxy(..), liftEffect)
15+
import Run (FProxy, Run, SProxy(..))
1716
import Run (interpret, lift, liftEffect, on, send) as Run
1817
import Type.Row (type (+))
1918
import WebRow.Contrib.Run (EffRow)
@@ -23,7 +22,7 @@ import WebRow.HTTP.Cookies (defaultAttributes, delete, lookup, lookupJson, set,
2322
import WebRow.KeyValueStore.Types (Key)
2423
import WebRow.Session.SessionStore (SessionStore)
2524
import WebRow.Session.SessionStore (hoist) as SessionStore
26-
import WebRow.Session.SessionStore.InMemory (lazy) as SessionStore.InMemory
25+
import WebRow.Session.SessionStore.InMemory (new) as SessionStore.InMemory
2726

2827
data SessionF session a
2928
= DeleteF (Boolean a)
@@ -71,31 +70,39 @@ cookieName = "session"
7170

7271
runInStore
7372
eff session.
74-
Lazy (Effect (SessionStore (Run (Cookies + EffRow + eff)) session))
73+
Effect (SessionStore (Run (Cookies + EffRow + eff)) session)
7574
Run (Cookies + EffRow + Session session + eff)
7675
~> Run (Cookies + EffRow + eff)
77-
runInStore store action = Run.interpret (Run.on _session (handleSession store) Run.send) action
78-
where
79-
handleSession
80-
Lazy (Effect (SessionStore (Run (Cookies + EffRow + eff)) session))
81-
SessionF session ~> Run (Cookies + EffRow + eff)
82-
handleSession ss (DeleteF next) = do
83-
void $ Cookies.delete cookieName
84-
Run.liftEffect (Lazy.force ss) >>= _.delete >>= next >>> pure
85-
86-
handleSession ss (FetchF next) = do
87-
ss' ← Run.liftEffect $ Lazy.force ss
88-
-- | TODO:
89-
-- | * Handle custom cookie attributes (expiration etc.).
90-
-- | * Should we raise here internalServerError when `set` returns `false`?
91-
-- | * Should we run testing cycle of test cookie setup?
92-
void $ Cookies.set cookieName { value: ss'.key, attributes: Cookies.defaultAttributes }
93-
ss'.fetch >>= next >>> pure
94-
95-
handleSession ss (SaveF v next) = do
96-
ss' ← Run.liftEffect $ Lazy.force ss
97-
void $ Cookies.set cookieName { value: ss'.key, attributes: Cookies.defaultAttributes }
98-
ss'.save v >>= next >>> pure
76+
runInStore store = runInRunStore (Run.liftEffect store)
77+
78+
runInRunStore
79+
eff session.
80+
Run (Cookies + EffRow + eff) (SessionStore (Run (Cookies + EffRow + eff)) session)
81+
Run (Cookies + EffRow + Session session + eff)
82+
~> Run (Cookies + EffRow + eff)
83+
runInRunStore store action = do
84+
s ← store
85+
let
86+
handleSession
87+
SessionF session ~> Run (Cookies + EffRow + eff)
88+
handleSession (DeleteF next) = do
89+
void $ Cookies.delete cookieName
90+
s.delete >>= next >>> pure
91+
92+
handleSession (FetchF next) = do
93+
-- | TODO:
94+
-- | * Handle custom cookie attributes (expiration etc.).
95+
-- | * Should we raise here internalServerError when `set` returns `false`?
96+
-- | * Should we run testing cycle of test cookie setup?
97+
void $ Cookies.set cookieName { value: s.key, attributes: Cookies.defaultAttributes }
98+
s.fetch >>= next >>> pure
99+
100+
handleSession (SaveF v next) = do
101+
void $ Cookies.set cookieName { value: s.key, attributes: Cookies.defaultAttributes }
102+
a ← s.save v
103+
pure (next a)
104+
105+
Run.interpret (Run.on _session handleSession Run.send) action
99106

100107
runInMemoryStore
101108
a eff session.
@@ -104,10 +111,11 @@ runInMemoryStore ∷
104111
Run (Cookies + EffRow + Session session + eff) a
105112
Run (Cookies + EffRow + eff) a
106113
runInMemoryStore ref defaultSession action = do
114+
-- | This laziness is a myth let's drop this all together
107115
lazySessionKey ← Cookies.lookup cookieName
108116
let
109-
effSessionStore = SessionStore.InMemory.lazy ref defaultSession lazySessionKey
110-
runInStore (map (SessionStore.hoist Run.liftEffect) <$> effSessionStore) action
117+
effSessionStore = SessionStore.InMemory.new ref defaultSession (Lazy.force lazySessionKey)
118+
runInStore (SessionStore.hoist Run.liftEffect <$> effSessionStore) action
111119

112120
-- | The whole session is stored in a cookie value so visible in the browser.
113121
-- | We don't need any key-value session store.
@@ -117,41 +125,38 @@ runInCookieValue ∷
117125
Run (Cookies + EffRow + eff) session
118126
Run (Cookies + EffRow + Session session + eff) a
119127
Run (Cookies + EffRow + eff) a
120-
runInCookieValue dual defaultSession action = do
121-
default ← defaultSession
128+
runInCookieValue dual defaultSession =
122129
let
123-
decode maybeRepr =
124-
fromMaybe default
125-
$ (maybeRepr >>= Pure.runValidator dual >>> toEither >>> hush)
126-
lazySession ← map decode <$> Cookies.lookupJson cookieName
127-
ref ← liftEffect $ Ref.new lazySession
128-
Run.interpret (Run.on _session (handleSession ref) Run.send) action
129-
where
130-
handleSession
131-
Ref (Lazy session)
132-
SessionF session ~> Run (Cookies + EffRow + eff)
133-
handleSession ref (DeleteF next) = do
134-
void $ Cookies.delete cookieName
135-
default ← defaultSession
136-
liftEffect $ Ref.write (defer \_ → default) ref
137-
pure (next true)
138-
139-
handleSession ref (FetchF next) = do
140-
lazySession ← liftEffect $ Ref.read ref
141-
let
142-
session = Lazy.force lazySession
143-
144-
json = Pure.runSerializer dual session
145-
-- | TODO:
146-
-- | * Handle custom cookie attributes (expiration etc.).
147-
-- | * Should we raise here internalServerError when `set` returns `false`?
148-
-- | * Should we run testing cycle of test cookie setup?
149-
void $ Cookies.setJson cookieName { json, attributes: Cookies.defaultAttributes }
150-
pure $ next session
151-
152-
handleSession ref (SaveF v next) = do
153-
lazySession ← liftEffect $ Ref.read ref
154-
let
155-
json = Pure.runSerializer dual v
156-
void $ Cookies.setJson cookieName { json, attributes: Cookies.defaultAttributes }
157-
pure (next true)
130+
fetchFromCookie = do
131+
default ← defaultSession
132+
map (decode default) <$> Cookies.lookupJson cookieName
133+
where
134+
decode default maybeRepr =
135+
fromMaybe default
136+
$ (maybeRepr >>= Pure.runValidator dual >>> toEither >>> hush)
137+
138+
handleSession SessionF session ~> Run (Cookies + EffRow + eff)
139+
handleSession (DeleteF next) = do
140+
void $ Cookies.delete cookieName
141+
pure (next true)
142+
143+
handleSession (FetchF next) = do
144+
session ← Lazy.force <$> fetchFromCookie
145+
let
146+
json = Pure.runSerializer dual session
147+
-- | TODO:
148+
-- | * Handle custom cookie attributes (expiration etc.).
149+
-- | * Should we raise here internalServerError when `set` returns `false`?
150+
-- | * Should we run testing cycle of test cookie setup?
151+
void $ Cookies.setJson cookieName { json, attributes: Cookies.defaultAttributes }
152+
pure $ next session
153+
154+
handleSession (SaveF v next) = do
155+
lazySession ← fetchFromCookie
156+
let
157+
json = Pure.runSerializer dual v
158+
void $ Cookies.setJson cookieName { json, attributes: Cookies.defaultAttributes }
159+
pure (next true)
160+
in
161+
Run.interpret (Run.on _session handleSession Run.send)
162+
Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
module WebRow.Session.SessionStore.InMemory where
22

33
import Prelude
4-
import Data.Lazy (Lazy)
5-
import Data.Lazy (defer, force) as Lazy
64
import Data.Map (Map)
75
import Data.Maybe (Maybe(..))
8-
import Debug.Trace (traceM)
96
import Effect (Effect)
107
import Effect.Ref (Ref)
118
import WebRow.KeyValueStore (Key)
@@ -22,13 +19,3 @@ new ref defaultSession =
2219
Just key → pure $ SessionStore.forKey defaultSession key kv
2320
NothingSessionStore.new defaultSession kv
2421

25-
-- | We can't be sure if Effect thunk is not run sequentially.
26-
-- | Because it seems that this is the case.
27-
lazy session. Ref (Map String session) session Lazy (Maybe Key) Lazy (Effect (SessionStore Effect session))
28-
lazy ref defaultSession mk =
29-
Lazy.defer \_ → do
30-
let
31-
kv = KeyValueStore.InMemory.forRef ref
32-
case Lazy.force mk of
33-
Just key → pure $ SessionStore.forKey defaultSession key kv
34-
NothingSessionStore.new defaultSession kv

src/WebRow/Testing/HTTP.purs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
module WebRow.Testing.HTTP where
22

33
import Prelude
4+
45
import Data.Array (singleton) as Array
6+
import Data.Either (Either(..))
57
import Data.Lazy (defer) as Lazy
68
import Data.List (List(..), reverse) as List
79
import Data.List (List)
@@ -40,12 +42,13 @@ import WebRow.HTTP.Cookies.Types (RequestCookies)
4042
import WebRow.HTTP.Request (Request)
4143
import WebRow.Routing (Routing, runRouting)
4244
import WebRow.Session (Session)
45+
import WebRow.Session (runInCookieValue) as Session
4346
import WebRow.Testing.HTTP.Cookies (toRequestCookies)
4447
import WebRow.Testing.HTTP.Response (Render) as Response
4548
import WebRow.Testing.HTTP.Response (Response) as Testing.HTTP
4649
import WebRow.Testing.HTTP.Response (runHTTPExcept, runRender, runSetHeader) as Testing.Response
4750
import WebRow.Testing.HTTP.Types (ClientCookies)
48-
import WebRow.Testing.Session (SessionStoreConfig)
51+
import WebRow.Testing.Session (SessionStoreConfig, SessionCookieConfig)
4952
import WebRow.Testing.Session (runInMemory) as Testing.Session
5053

5154
-- | TODO: Upgrdae to polymorphic `body` type here when
@@ -179,25 +182,29 @@ run ∷
179182
eff
180183
eff_
181184
(S.Producer (Exchange res) + eff)
182-
SessionStoreConfig session
185+
-- (Either (SessionStoreConfig session) (SessionCookieConfig session)) →
186+
(SessionStoreConfig session)
183187
RouteDuplex' routes
184188
Response.Render (Server session routes res + eff) String res
185189
Run (Server session routes res + eff) res
186190
Run (AffRow + Client session res + EffRow + eff) Unit
187191
Run (AffRow + EffRow + eff) (History res)
188192
run sessionStoreConfig routeDuplex render server client = do
189-
Testing.Session.runInMemory sessionStoreConfig
193+
let
194+
runSession = Testing.Session.runInMemory sessionStoreConfig
195+
196+
runSession
190197
$ evalStateAt _httpSession (mempty ClientCookies)
191-
$ httpSession
198+
$ httpExchange
192199
where
193200
-- | This can be a bit unintuitive but we have to expand
194201
-- | row with another yield so the consumer `take' 100`
195202
-- | can swallow it.
196-
-- go' = Run.expand go
197-
httpSession = go # Pull.feed (S.P.take 100) # S.P.fold (flip List.Cons) List.Nil List.reverse
203+
httpExchange Run (AffRow + EffRow + HTTPSession + Session session + eff) (List (Exchange res))
204+
httpExchange = go # Pull.feed (S.P.take 100) # S.P.fold (flip List.Cons) List.Nil List.reverse
198205

199206
-- | I don't really need this signature here but
200-
-- | maybe it can be a small hint what is going on here.
207+
-- | maybe it can be a small hint what is going on.
201208
go
202209
Run
203210
( AffRow

src/WebRow/Testing/HTTP/Response.purs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
module WebRow.Testing.HTTP.Response where
22

33
import Prelude
4+
5+
import Data.Maybe (Maybe(..))
46
import HTTPure (Headers, header) as HTTPure
7+
import HTTPure (Status)
58
import HTTPure.Body (class Body) as HTTPure
69
import HTTPure.Headers (empty) as HTTPure.Headers
7-
import Prim.Row (class Union) as Row
810
import Run (Run)
911
import Run (on, run, send) as Run
1012
import Run.Except (catchAt)
@@ -21,6 +23,14 @@ data Response body res
2123

2224
derive instance functorResponseFunctor (Response body)
2325

26+
status body res. Response body res Maybe Status
27+
status (HTTPResponse { parts: { status: s }}) = Just s
28+
status _ = Nothing
29+
30+
body body res. Response body res Maybe body
31+
body (HTTPResponse { parts: { body: b }}) = Just b
32+
body _ = Nothing
33+
2434
-- type Response' body res = Response body (Variant res)
2535
type Render eff body res
2636
= res Run eff (HTTPResponse body)

src/WebRow/Testing/Session.purs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module WebRow.Testing.Session where
22

33
import Prelude
4-
import Data.Lazy (defer)
5-
import Data.Lazy (force) as Lazy
4+
5+
import Data.Argonaut (Json)
66
import Data.Map (Map)
77
import Data.Maybe (Maybe)
88
import Effect (Effect)
99
import Effect.Ref (Ref)
10+
import Polyform.Validator.Dual.Pure (Dual) as Pure
1011
import Run (Run)
1112
import Run (interpret, liftEffect, on, send) as Run
1213
import Type.Row (type (+))
@@ -15,7 +16,7 @@ import WebRow.Forms.Payload (Key)
1516
import WebRow.Session (Session, SessionF(..), _session)
1617
import WebRow.Session.SessionStore (SessionStore)
1718
import WebRow.Session.SessionStore (hoist) as SessionStore
18-
import WebRow.Session.SessionStore.InMemory (lazy) as SessionStore.InMemory
19+
import WebRow.Session.SessionStore.InMemory (new) as SessionStore.InMemory
1920

2021
type SessionStoreConfig session
2122
= { default session
@@ -48,7 +49,12 @@ runInMemory ∷
4849
Run (EffRow + eff) a
4950
runInMemory { default, key, ref } action = do
5051
let
51-
ss = SessionStore.InMemory.lazy ref default (defer \_ → key)
52+
ss = SessionStore.InMemory.new ref default key
5253

53-
ss' = map (SessionStore.hoist Run.liftEffect) $ Lazy.force ss
54+
ss' = map (SessionStore.hoist Run.liftEffect) $ ss
5455
run ss' action
56+
57+
type SessionCookieConfig session
58+
= { default session
59+
, dual err. Pure.Dual err Json session
60+
}

test/Main.purs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import Test.Spec.Runner (runSpec)
99
import Test.WebRow.Applets (spec) as Applets
1010
import Test.WebRow.HTTP (spec) as HTTP
1111
import Test.WebRow.I18N (spec) as I18N
12+
import Test.WebRow.Session (spec) as Session
1213

1314
main :: Effect Unit
1415
main = launchAff_ $ runSpec [consoleReporter] do
1516
I18N.spec
1617
Applets.spec
1718
HTTP.spec
19+
Session.spec

0 commit comments

Comments
 (0)