Skip to content

Commit 036dda2

Browse files
author
André Videla
committed
add idris2 version
1 parent ef7bb67 commit 036dda2

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
*.idr~
44
.DS_Store
55
examples/main
6+
7+
build/

idris2/Main.idr

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module Main
2+
3+
import Server
4+
5+
Operations : Path
6+
Operations = "Operation" // Cap "fst" Int // Split [
7+
"add" // Cap "b" Int // Returns Int Get Ok,
8+
"div" // Cap "denominator" Int // Returns Int Get Ok
9+
]
10+
11+
Range : Path
12+
Range = "Range" // Returns (List Int) Get Ok
13+
14+
implEx : Signature Range
15+
implEx = ?rest
16+
-- implEx : Signature (Split [Range, Operations])
17+
-- implEx = ?rest
18+
19+
main : IO ()
20+
main = newServer Range implEx

idris2/Server.idr

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
module Server
2+
3+
import public Data.Vect
4+
import Data.List
5+
import Data.Strings
6+
7+
%default total
8+
9+
public export
10+
data Verb = Get | Patch | Post | Put
11+
12+
public export
13+
data StatusCode : Nat -> Type where
14+
-- 2xx
15+
Ok : StatusCode 200
16+
Created : StatusCode 201
17+
Accepted : StatusCode 202
18+
-- 3xx
19+
Mutliple : StatusCode 300
20+
Moved : StatusCode 301
21+
22+
-- 4xx
23+
Bad : StatusCode 400
24+
Unauthorized : StatusCode 401
25+
-- Payment : StatusCode 402
26+
Forbidden : StatusCode 403
27+
NotFound : StatusCode 404
28+
29+
export
30+
Parser : Type -> Type
31+
Parser t = String -> Maybe t
32+
33+
public export
34+
interface HasParser t where
35+
parse : Parser t
36+
37+
export
38+
HasParser Int where
39+
parse = ?parseInteger
40+
41+
export
42+
HasParser String where
43+
parse = Just
44+
45+
||| A Type for Paths that can have a common prefix.
46+
public export
47+
data Path : Type where
48+
Returns : (t : Type) -> Show t => (v : Verb) -> (s : StatusCode n) -> Path
49+
Plain : String -> (ps : Path) -> Path
50+
Capture : (name : String) -> (t : Type) -> HasParser t => (ps : Path) -> Path
51+
Split : List Path -> Path
52+
53+
||| A Type for full path components.
54+
public export
55+
data PathComp : Nat -> Type where
56+
End : (ret : Type) -> Show ret => PathComp Z
57+
Str : String -> PathComp n -> PathComp (S n)
58+
Tpe : (t : Type) -> HasParser t => PathComp n -> PathComp (S n)
59+
60+
||| Convert a PathComp into a function type
61+
public export
62+
PathCompToType : PathComp n -> Type
63+
PathCompToType (End ret) = ret
64+
PathCompToType (Str x y) = PathCompToType y
65+
PathCompToType (Tpe x y) = x -> PathCompToType y
66+
67+
||| Convert a PathComp into a tuple of arguments for the corresponding function type
68+
Args : PathComp n -> Type
69+
Args (End t) = ()
70+
Args (Str s ps) = Args ps
71+
Args (Tpe t ps) = (t, Args ps)
72+
73+
||| Returns the return type of the corresponding function type from a PathComp
74+
Ret : PathComp n -> Type
75+
Ret (End t) = t
76+
Ret (Str _ ps) = Ret ps
77+
Ret (Tpe _ ps) = Ret ps
78+
79+
||| Converts a PathComp into an uncurried function from a tuple of argument into its return type
80+
PathToArgs : PathComp n -> Type
81+
PathToArgs path = Args path -> Ret path
82+
83+
||| Converts a function type into its uncurried variant
84+
convertPathFuncs : {path : PathComp n} -> PathCompToType path -> Args path -> Ret path
85+
convertPathFuncs x y {path = (End ret)} = x
86+
convertPathFuncs x y {path = (Str s p)} = convertPathFuncs x y {path=p}
87+
convertPathFuncs f (v, args) {path = (Tpe t p)} = convertPathFuncs (f v) args {path=p}
88+
89+
||| A List of functions indexed by a list of PathComp which define each function's signature
90+
public export
91+
data PathList : List (n ** PathComp n) -> Type where
92+
Nil : PathList []
93+
(::) : (p : PathCompToType (DPair.snd t)) -> PathList ts -> PathList (t :: ts)
94+
95+
infixr 5 //
96+
97+
public export
98+
data Capt : Type where
99+
Cap : (name : String) -> (t : Type) -> HasParser t => Capt
100+
101+
export
102+
interface PathBuilder t where
103+
(//) : t -> Path -> Path
104+
105+
public export
106+
PathBuilder String where
107+
(//) = Plain
108+
109+
public export
110+
PathBuilder Capt where
111+
(//) (Cap n t) p = Capture n t p
112+
113+
public export
114+
TypeList : Type
115+
TypeList = List (Either String (s : Type ** HasParser s))
116+
117+
public export
118+
mkComponents : TypeList -> (t : Type) -> Show t => (n ** PathComp n)
119+
mkComponents [] x = (Z ** End x)
120+
mkComponents ((Left l) :: xs) x = let (n ** ys) = mkComponents xs x in (S n ** Str l ys)
121+
mkComponents ((Right (r ** s)) :: xs) x = let (n ** ys) = mkComponents xs x in (S n ** Tpe r ys)
122+
123+
||| Maps a Path to a list of PathComponents.
124+
||| The prefix is repeated for each branching path
125+
||| in the resulting list
126+
|||
127+
||| The following path:
128+
||| a━━b━━c
129+
||| ┣━━d
130+
||| ┗━━e━━f
131+
||| ┗━━g
132+
||| Will be mapped into
133+
|||
134+
||| [ a━━b━━c
135+
||| , a━━b━━d
136+
||| , a━━b━━e━━f
137+
||| , a━━b━━e━━g
138+
||| ]
139+
public export
140+
toComponents : (pre : TypeList) -> (path : Path) -> List (n ** PathComp n)
141+
toComponents pre (Returns t v s) = pure $ mkComponents (reverse pre) t
142+
toComponents pre (Plain name ps) = toComponents (Left name :: pre) ps
143+
toComponents pre (Capture name t ps) = toComponents (Right (t ** %search) :: pre) ps
144+
toComponents pre (Split xs) = xs >>= assert_total (toComponents pre)
145+
146+
||| Maps a Path into a list of functions
147+
|||
148+
||| The common prefix for paths splittingis duplicated between each function
149+
||| so that the user of the API doesn't have to concern themselves with
150+
||| the structure of the API and implement and avoid implementing partially
151+
||| applied functions.
152+
public export
153+
Signature : Path -> Type
154+
Signature path = PathList (toComponents [] path)
155+
156+
FromSignature : {path : Path} -> Signature path -> List (n ** p : PathComp n ** (Args p -> Ret p))
157+
FromSignature ps {path} = signatureHelp ps
158+
where
159+
signatureHelp : {fnPath : List (m ** PathComp m)} -> PathList fnPath
160+
-> List (n ** p : PathComp n ** (Args p -> Ret p))
161+
signatureHelp [] {fnPath = []} = []
162+
signatureHelp (p :: ps) {fnPath = ((n ** path) :: ts)} =
163+
(n ** path ** convertPathFuncs p) :: signatureHelp ps
164+
165+
makeParser : (path : PathComp n)
166+
-> Vect n String -> Maybe (Args path)
167+
makeParser (End t) [] = Just ()
168+
makeParser (Str s ps) (z :: xs) = if s == z then makeParser ps xs else Nothing
169+
makeParser (Tpe t ps) (z :: xs) =
170+
[| MkPair (parse {t} z) (makeParser ps xs) |]
171+
172+
Handler : PathComp n -> Type
173+
Handler path = Args path -> Ret path
174+
175+
-- Here the handlers are both parsing the string and returning the result of
176+
-- the comutation as an encoded string. This is to void leaking the detail of
177+
-- the dependency between the parsed type and the type of the handler.
178+
partial
179+
server : (handlers : List (List String -> Maybe String)) -> IO ()
180+
server handlers = do
181+
str <- getLine
182+
let Just result = tryAll handlers (split (== '/') str)
183+
| _ => putStrLn ("could not parse " ++ str)
184+
putStrLn result
185+
server handlers
186+
where
187+
tryAll : List (List String -> Maybe String) -> List String -> Maybe String
188+
tryAll [] input = Nothing
189+
tryAll (f :: fs) input = f input <|> tryAll fs input
190+
191+
||| Returns a printing function for the return type of a given PathComp
192+
pathCompToPrintRet : (p : PathComp n) -> (Ret p) -> String
193+
pathCompToPrintRet (End ret) x = show x
194+
pathCompToPrintRet (Str _ ps) x = pathCompToPrintRet ps x
195+
pathCompToPrintRet (Tpe _ ps) x = pathCompToPrintRet ps x
196+
197+
||| Make a function to handle server request from a path
198+
|||
199+
||| This combines 3 functions, one to parse the URL path and
200+
||| extract the arguments from it. One to perform an operation on the
201+
||| parsed arguments, and one to print the final result.
202+
||| The resulting functions hides all details about the intermediate
203+
||| types used to parse the incoming request and operates on Strings
204+
||| only.
205+
||| @ length : The length of the path
206+
||| @ path : The URL path
207+
||| @ showResult : The printer function for the computed result
208+
||| @ handler : The operation to perform on the parsed arguments
209+
stringSig : (length : Nat) -> (path : PathComp length)
210+
-> (parsePath : Vect length String -> Maybe (Args p))
211+
-> (showResult : Ret p -> String)
212+
-> (handler : Args p -> Ret p)
213+
-> (List String -> Maybe String)
214+
stringSig n p parser printer handler =
215+
map (printer . handler) . (\x => checkLength n x >>= parser)
216+
where
217+
checkLength : (n : Nat) -> List a -> Maybe (Vect n a)
218+
checkLength n ls = exactLength n $ fromList ls
219+
220+
||| Given an implementation of a path, return a list of function for each possible route
221+
handleAllPaths : {path : List (m ** PathComp m)} -> PathList path -> List (List String -> Maybe String)
222+
handleAllPaths [] {path = []} = []
223+
handleAllPaths (v :: vs) {path = ((n ** comp) :: ts)} =
224+
stringSig n comp (makeParser comp) (pathCompToPrintRet comp) (convertPathFuncs v)
225+
:: handleAllPaths vs {path=ts}
226+
227+
||| Given an implementation of a path, return a list of function for each possible route
228+
forAllPaths : {path : Path} -> Signature path -> List (List String -> Maybe String)
229+
forAllPaths x {path} = handleAllPaths x
230+
231+
||| Instanciate a new sever given a path and an implementation for it.
232+
|||
233+
||| @ path : The server's API as a Path
234+
||| @ impl : The servver's implementation of the exposed API
235+
export partial
236+
newServer : (path : Path)
237+
-> (impl : Signature path)
238+
-> IO ()
239+
newServer path impl = server (forAllPaths impl)
240+

0 commit comments

Comments
 (0)