@@ -45,7 +45,8 @@ camelCase = camelCase' . Prelude.map (replace "-" "")
45 capitalize "" = ""
45 capitalize "" = ""
46 capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name
46 capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name
48+type ForeignType = Text
48−type Arg = Text
49+type Arg = (Text, ForeignType)
50newtype Segment = Segment { _segment :: SegmentType }
51newtype Segment = Segment { _segment :: SegmentType }
51 deriving (Eq, Show)
52 deriving (Eq, Show)
@@ -68,10 +69,10 @@ data QueryArg = QueryArg
68 } deriving (Eq, Show)
69 } deriving (Eq, Show)
70data HeaderArg = HeaderArg
71data HeaderArg = HeaderArg
71− { headerArgName :: Text
72+ { headerArg :: Arg
73 | ReplaceHeaderArg
74 | ReplaceHeaderArg
74− { headerArgName :: Text
75+ { headerArg :: Arg
75 , headerPattern :: Text
76 , headerPattern :: Text
76 } deriving (Eq, Show)
77 } deriving (Eq, Show)
@@ -88,11 +89,12 @@ type FunctionName = [Text]
88type Method = Text
89type Method = Text
90data Req = Req
91data Req = Req
91− { _reqUrl :: Url
92+ { _reqUrl :: Url
92− , _reqMethod :: Method
93+ , _reqMethod :: Method
93− , _reqHeaders :: [HeaderArg]
94+ , _reqHeaders :: [HeaderArg]
94− , _reqBody :: Bool
95+ , _reqBody :: Maybe ForeignType
96+ , _reqReturnType :: ForeignType
95− , _funcName :: FunctionName
97+ , _funcName :: FunctionName
96 } deriving (Eq, Show)
98 } deriving (Eq, Show)
98makeLenses ''QueryArg
100makeLenses ''QueryArg
@@ -109,7 +111,7 @@ captureArg (Segment (Cap s)) = s
109captureArg _ = error "captureArg called on non capture"
111captureArg _ = error "captureArg called on non capture"
111defReq :: Req
113defReq :: Req
112−defReq = Req defUrl "GET" [] False []
114+defReq = Req defUrl "GET" [] Nothing "" []
114-- | To be used exclusively as a "negative" return type/constraint
116-- | To be used exclusively as a "negative" return type/constraint
115-- by @'Elem`@ type family.
117-- by @'Elem`@ type family.
@@ -120,142 +122,228 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where
120 Elem a (a ': list) = ()
122 Elem a (a ': list) = ()
121 Elem a (b ': list) = Elem a list
123 Elem a (b ': list) = Elem a list
125+-- | 'HasForeignType' maps Haskell types with types in the target
126+-- language of your backend. For example, let's say you're
127+-- implementing a backend to some language __X__:
129+-- > -- First you need to create a dummy type to parametrize your
133+-- > -- Otherwise you define instances for the types you need
134+-- > instance HasForeignType LangX Int where
135+-- > typeFor _ _ = "intX"
137+-- > -- Or for example in case of lists
138+-- > instance HasForeignType LangX a => HasForeignType LangX [a] where
139+-- > typeFor lang _ = "listX of " <> typeFor lang (Proxy :: Proxy a)
141+-- Finally to generate list of information about all the endpoints for
142+-- an API you create a function of a form:
144+-- > getEndpoints :: (HasForeign LangX api, GenerateList (Foreign api))
145+-- > => Proxy api -> [Req]
146+-- > getEndpoints api = listFromAPI (Proxy :: Proxy LangX) api
148+-- > -- If language __X__ is dynamically typed then you can use
149+-- > -- a predefined NoTypes parameter
150+-- > getEndpoints :: (HasForeign NoTypes api, GenerateList (Foreign api))
151+-- > => Proxy api -> [Req]
152+-- > getEndpoints api = listFromAPI (Proxy :: Proxy NoTypes) api
155+class HasForeignType lang a where
156+ typeFor :: Proxy lang -> Proxy a -> ForeignType
160+instance HasForeignType NoTypes a where
123−class HasForeign (layout :: *) where
163+class HasForeign lang (layout :: *) where
124 type Foreign layout :: *
164 type Foreign layout :: *
125− foreignFor :: Proxy layout -> Req -> Foreign layout
165+ foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout
127−instance (HasForeign a, HasForeign b)
167+instance (HasForeign lang a, HasForeign lang b)
128− => HasForeign (a :<|> b) where
168+ => HasForeign lang (a :<|> b) where
129 type Foreign (a :<|> b) = Foreign a :<|> Foreign b
169 type Foreign (a :<|> b) = Foreign a :<|> Foreign b
131− foreignFor Proxy req =
171+ foreignFor lang Proxy req =
132− foreignFor (Proxy :: Proxy a) req
172+ foreignFor lang (Proxy :: Proxy a) req
133− :<|> foreignFor (Proxy :: Proxy b) req
173+ :<|> foreignFor lang (Proxy :: Proxy b) req
135−instance (KnownSymbol sym, HasForeign sublayout)
175+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
136− => HasForeign (Capture sym a :> sublayout) where
176+ => HasForeign lang (Capture sym a :> sublayout) where
137 type Foreign (Capture sym a :> sublayout) = Foreign sublayout
177 type Foreign (Capture sym a :> sublayout) = Foreign sublayout
139− foreignFor Proxy req =
179+ foreignFor lang Proxy req =
140− foreignFor (Proxy :: Proxy sublayout) $
180+ foreignFor lang (Proxy :: Proxy sublayout) $
141− req & reqUrl.path <>~ [Segment (Cap str)]
181+ req & reqUrl.path <>~ [Segment (Cap arg)]
142 & funcName %~ (++ ["by", str])
182 & funcName %~ (++ ["by", str])
144− where str = pack . symbolVal $ (Proxy :: Proxy sym)
185+ str = pack . symbolVal $ (Proxy :: Proxy sym)
186+ arg = (str, typeFor lang (Proxy :: Proxy a))
188+instance (Elem JSON list, HasForeignType lang a)
146−instance Elem JSON list => HasForeign (Delete list a) where
189+ => HasForeign lang (Delete list a) where
147 type Foreign (Delete list a) = Req
190 type Foreign (Delete list a) = Req
149− foreignFor Proxy req =
192+ foreignFor lang Proxy req =
150− req & funcName %~ ("delete" :)
193+ req & funcName %~ ("delete" :)
151− & reqMethod .~ "DELETE"
194+ & reqMethod .~ "DELETE"
195+ & reqReturnType .~ retType
197+ retType = typeFor lang (Proxy :: Proxy a)
199+instance (Elem JSON list, HasForeignType lang a)
153−instance Elem JSON list => HasForeign (Get list a) where
200+ => HasForeign lang (Get list a) where
154 type Foreign (Get list a) = Req
201 type Foreign (Get list a) = Req
156− foreignFor Proxy req =
203+ foreignFor lang Proxy req =
157 req & funcName %~ ("get" :)
204 req & funcName %~ ("get" :)
158 & reqMethod .~ "GET"
205 & reqMethod .~ "GET"
206+ & reqReturnType .~ retType
208+ retType = typeFor lang (Proxy :: Proxy a)
160−instance (KnownSymbol sym, HasForeign sublayout)
210+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
161− => HasForeign (Header sym a :> sublayout) where
211+ => HasForeign lang (Header sym a :> sublayout) where
162 type Foreign (Header sym a :> sublayout) = Foreign sublayout
212 type Foreign (Header sym a :> sublayout) = Foreign sublayout
164− foreignFor Proxy req =
214+ foreignFor lang Proxy req =
215+ foreignFor lang subP $ req
165− foreignFor subP (req & reqHeaders <>~ [HeaderArg hname])
216+ & reqHeaders <>~ [HeaderArg arg]
167− where hname = pack . symbolVal $ (Proxy :: Proxy sym)
219+ hname = pack . symbolVal $ (Proxy :: Proxy sym)
220+ arg = (hname, typeFor lang (Proxy :: Proxy a))
168− subP = Proxy :: Proxy sublayout
221+ subP = Proxy :: Proxy sublayout
223+instance (Elem JSON list, HasForeignType lang a)
170−instance Elem JSON list => HasForeign (Post list a) where
224+ => HasForeign lang (Post list a) where
171 type Foreign (Post list a) = Req
225 type Foreign (Post list a) = Req
173− foreignFor Proxy req =
227+ foreignFor lang Proxy req =
174 req & funcName %~ ("post" :)
228 req & funcName %~ ("post" :)
175 & reqMethod .~ "POST"
229 & reqMethod .~ "POST"
230+ & reqReturnType .~ retType
232+ retType = typeFor lang (Proxy :: Proxy a)
234+instance (Elem JSON list, HasForeignType lang a)
177−instance Elem JSON list => HasForeign (Put list a) where
235+ => HasForeign lang (Put list a) where
178 type Foreign (Put list a) = Req
236 type Foreign (Put list a) = Req
180− foreignFor Proxy req =
238+ foreignFor lang Proxy req =
181 req & funcName %~ ("put" :)
239 req & funcName %~ ("put" :)
182 & reqMethod .~ "PUT"
240 & reqMethod .~ "PUT"
241+ & reqReturnType .~ retType
243+ retType = typeFor lang (Proxy :: Proxy a)
184−instance (KnownSymbol sym, HasForeign sublayout)
245+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
185− => HasForeign (QueryParam sym a :> sublayout) where
246+ => HasForeign lang (QueryParam sym a :> sublayout) where
186 type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
247 type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
188− foreignFor Proxy req =
249+ foreignFor lang Proxy req =
189− foreignFor (Proxy :: Proxy sublayout) $
250+ foreignFor lang (Proxy :: Proxy sublayout) $
190− req & reqUrl.queryStr <>~ [QueryArg str Normal]
251+ req & reqUrl.queryStr <>~ [QueryArg arg Normal]
192− where str = pack . symbolVal $ (Proxy :: Proxy sym)
254+ str = pack . symbolVal $ (Proxy :: Proxy sym)
255+ arg = (str, typeFor lang (Proxy :: Proxy a))
194−instance (KnownSymbol sym, HasForeign sublayout)
257+instance (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout)
195− => HasForeign (QueryParams sym a :> sublayout) where
258+ => HasForeign lang (QueryParams sym a :> sublayout) where
196 type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
259 type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
198− foreignFor Proxy req =
261+ foreignFor lang Proxy req =
199− foreignFor (Proxy :: Proxy sublayout) $
262+ foreignFor lang (Proxy :: Proxy sublayout) $
200− req & reqUrl.queryStr <>~ [QueryArg str List]
263+ req & reqUrl.queryStr <>~ [QueryArg arg List]
202− where str = pack . symbolVal $ (Proxy :: Proxy sym)
266+ str = pack . symbolVal $ (Proxy :: Proxy sym)
267+ arg = (str, typeFor lang (Proxy :: Proxy [a]))
204−instance (KnownSymbol sym, HasForeign sublayout)
269+instance (KnownSymbol sym, HasForeignType lang a, a ~ Bool, HasForeign lang sublayout)
205− => HasForeign (QueryFlag sym :> sublayout) where
270+ => HasForeign lang (QueryFlag sym :> sublayout) where
206 type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
271 type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
208− foreignFor Proxy req =
273+ foreignFor lang Proxy req =
209− foreignFor (Proxy :: Proxy sublayout) $
274+ foreignFor lang (Proxy :: Proxy sublayout) $
210− req & reqUrl.queryStr <>~ [QueryArg str Flag]
275+ req & reqUrl.queryStr <>~ [QueryArg arg Flag]
212− where str = pack . symbolVal $ (Proxy :: Proxy sym)
278+ str = pack . symbolVal $ (Proxy :: Proxy sym)
279+ arg = (str, typeFor lang (Proxy :: Proxy a))
214−instance HasForeign Raw where
281+instance HasForeign lang Raw where
215 type Foreign Raw = Method -> Req
282 type Foreign Raw = Method -> Req
217− foreignFor Proxy req method =
284+ foreignFor _ Proxy req method =
218 req & funcName %~ ((toLower method) :)
285 req & funcName %~ ((toLower method) :)
219 & reqMethod .~ method
286 & reqMethod .~ method
288+instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout)
221−instance (Elem JSON list, HasForeign sublayout) => HasForeign (ReqBody list a :> sublayout) where
289+ => HasForeign lang (ReqBody list a :> sublayout) where
222 type Foreign (ReqBody list a :> sublayout) = Foreign sublayout
290 type Foreign (ReqBody list a :> sublayout) = Foreign sublayout
224− foreignFor Proxy req =
292+ foreignFor lang Proxy req =
225− foreignFor (Proxy :: Proxy sublayout) $
293+ foreignFor lang (Proxy :: Proxy sublayout) $
226− req & reqBody .~ True
294+ req & reqBody .~ (Just $ typeFor lang (Proxy :: Proxy a))
228−instance (KnownSymbol path, HasForeign sublayout)
296+instance (KnownSymbol path, HasForeign lang sublayout)
229− => HasForeign (path :> sublayout) where
297+ => HasForeign lang (path :> sublayout) where
230 type Foreign (path :> sublayout) = Foreign sublayout
298 type Foreign (path :> sublayout) = Foreign sublayout
232− foreignFor Proxy req =
300+ foreignFor lang Proxy req =
233− foreignFor (Proxy :: Proxy sublayout) $
301+ foreignFor lang (Proxy :: Proxy sublayout) $
234 req & reqUrl.path <>~ [Segment (Static str)]
302 req & reqUrl.path <>~ [Segment (Static str)]
235 & funcName %~ (++ [str])
303 & funcName %~ (++ [str])
237− where str = Data.Text.map (\c -> if c == '.' then '_' else c) . pack . symbolVal $ (Proxy :: Proxy path)
306+ str = Data.Text.map (\c -> if c == '.' then '_' else c)
307+ . pack . symbolVal $ (Proxy :: Proxy path)
239−instance HasForeign sublayout => HasForeign (RemoteHost :> sublayout) where
309+instance HasForeign lang sublayout => HasForeign lang (RemoteHost :> sublayout) where
240 type Foreign (RemoteHost :> sublayout) = Foreign sublayout
310 type Foreign (RemoteHost :> sublayout) = Foreign sublayout
242− foreignFor Proxy req =
312+ foreignFor lang Proxy req =
243− foreignFor (Proxy :: Proxy sublayout) req
313+ foreignFor lang (Proxy :: Proxy sublayout) req
245−instance HasForeign sublayout => HasForeign (IsSecure :> sublayout) where
315+instance HasForeign lang sublayout => HasForeign lang (IsSecure :> sublayout) where
246 type Foreign (IsSecure :> sublayout) = Foreign sublayout
316 type Foreign (IsSecure :> sublayout) = Foreign sublayout
248− foreignFor Proxy req =
318+ foreignFor lang Proxy req =
249− foreignFor (Proxy :: Proxy sublayout) req
319+ foreignFor lang (Proxy :: Proxy sublayout) req
251−instance HasForeign sublayout => HasForeign (Vault :> sublayout) where
321+instance HasForeign lang sublayout => HasForeign lang (Vault :> sublayout) where
252 type Foreign (Vault :> sublayout) = Foreign sublayout
322 type Foreign (Vault :> sublayout) = Foreign sublayout
254− foreignFor Proxy req =
324+ foreignFor lang Proxy req =
255− foreignFor (Proxy :: Proxy sublayout) req
325+ foreignFor lang (Proxy :: Proxy sublayout) req
257−instance HasForeign sublayout => HasForeign (HttpVersion :> sublayout) where
327+instance HasForeign lang sublayout => HasForeign lang (HttpVersion :> sublayout) where
258 type Foreign (HttpVersion :> sublayout) = Foreign sublayout
328 type Foreign (HttpVersion :> sublayout) = Foreign sublayout
260− foreignFor Proxy req =
330+ foreignFor lang Proxy req =
261− foreignFor (Proxy :: Proxy sublayout) req
331+ foreignFor lang (Proxy :: Proxy sublayout) req
333+-- | Utility class used by 'listFromAPI' which computes
334+-- the data needed to generate a function for each endpoint
335+-- and hands it all back in a list.
336+class GenerateList reqs where
337+ generateList :: reqs -> [Req]
339+instance GenerateList Req where
340+ generateList r = [r]
342+instance (GenerateList start, GenerateList rest) => GenerateList (start :<|> rest) where
343+ generateList (start :<|> rest) = (generateList start) ++ (generateList rest)
345+-- | Generate the necessary data for codegen as a list, each 'Req'
346+-- describing one endpoint from your API type.
347+listFromAPI :: (HasForeign lang api, GenerateList (Foreign api)) => Proxy lang -> Proxy api -> [Req]
348+listFromAPI lang p = generateList (foreignFor lang p defReq)