|
| 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