@@ -45,7 +45,8 @@ camelCase = camelCase' . Prelude.map (replace "-" "")
4545 capitalize "" = ""
4646 capitalize name = C.toUpper (Data.Text.head name) `cons` Data.Text.tail name
4747
48−type Arg = Text
48+type ForeignType = Text
49+type Arg = (Text, ForeignType)
4950
5051newtype Segment = Segment { _segment :: SegmentType }
5152 deriving (Eq, Show)
@@ -68,10 +69,10 @@ data QueryArg = QueryArg
6869 } deriving (Eq, Show)
6970
7071data HeaderArg = HeaderArg
71− { headerArgName :: Text
72+ { headerArg :: Arg
7273 }
7374 | ReplaceHeaderArg
74− { headerArgName :: Text
75+ { headerArg :: Arg
7576 , headerPattern :: Text
7677 } deriving (Eq, Show)
7778
@@ -88,11 +89,12 @@ type FunctionName = [Text]
8889type Method = Text
8990
9091data Req = Req
91− { _reqUrl :: Url
92− , _reqMethod :: Method
93− , _reqHeaders :: [HeaderArg]
94− , _reqBody :: Bool
95− , _funcName :: FunctionName
92+ { _reqUrl :: Url
93+ , _reqMethod :: Method
94+ , _reqHeaders :: [HeaderArg]
95+ , _reqBody :: Maybe ForeignType
96+ , _reqReturnType :: ForeignType
97+ , _funcName :: FunctionName
9698 } deriving (Eq, Show)
9799
98100makeLenses ''QueryArg
@@ -109,7 +111,7 @@ captureArg (Segment (Cap s)) = s
109111captureArg _ = error "captureArg called on non capture"
110112
111113defReq :: Req
112−defReq = Req defUrl "GET" [] False []
114+defReq = Req defUrl "GET" [] Nothing "" []
113115
114116-- | To be used exclusively as a "negative" return type/constraint
115117-- by @'Elem`@ type family.
@@ -120,142 +122,228 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where
120122 Elem a (a ': list) = ()
121123 Elem a (b ': list) = Elem a list
122124
123−class HasForeign (layout :: *) where
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__:
128+--
129+-- > -- First you need to create a dummy type to parametrize your
130+-- > -- instances.
131+-- > data LangX
132+-- >
133+-- > -- Otherwise you define instances for the types you need
134+-- > instance HasForeignType LangX Int where
135+-- > typeFor _ _ = "intX"
136+-- >
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)
140+--
141+-- Finally to generate list of information about all the endpoints for
142+-- an API you create a function of a form:
143+--
144+-- > getEndpoints :: (HasForeign LangX api, GenerateList (Foreign api))
145+-- > => Proxy api -> [Req]
146+-- > getEndpoints api = listFromAPI (Proxy :: Proxy LangX) api
147+--
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
153+-- >
154+--
155+class HasForeignType lang a where
156+ typeFor :: Proxy lang -> Proxy a -> ForeignType
157+
158+data NoTypes
159+
160+instance HasForeignType NoTypes a where
161+ typeFor _ _ = empty
162+
163+class HasForeign lang (layout :: *) where
124164 type Foreign layout :: *
125− foreignFor :: Proxy layout -> Req -> Foreign layout
165+ foreignFor :: Proxy lang -> Proxy layout -> Req -> Foreign layout
126166
127−instance (HasForeign a, HasForeign b)
128− => HasForeign (a :<|> b) where
167+instance (HasForeign lang a, HasForeign lang b)
168+ => HasForeign lang (a :<|> b) where
129169 type Foreign (a :<|> b) = Foreign a :<|> Foreign b
130170
131− foreignFor Proxy req =
132− foreignFor (Proxy :: Proxy a) req
133− :<|> foreignFor (Proxy :: Proxy b) req
171+ foreignFor lang Proxy req =
172+ foreignFor lang (Proxy :: Proxy a) req
173+ :<|> foreignFor lang (Proxy :: Proxy b) req
134174
135−instance (KnownSymbol sym, HasForeign sublayout)
136− => HasForeign (Capture sym a :> sublayout) where
175+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
176+ => HasForeign lang (Capture sym a :> sublayout) where
137177 type Foreign (Capture sym a :> sublayout) = Foreign sublayout
138178
139− foreignFor Proxy req =
140− foreignFor (Proxy :: Proxy sublayout) $
141− req & reqUrl.path <>~ [Segment (Cap str)]
179+ foreignFor lang Proxy req =
180+ foreignFor lang (Proxy :: Proxy sublayout) $
181+ req & reqUrl.path <>~ [Segment (Cap arg)]
142182 & funcName %~ (++ ["by", str])
143183
144− where str = pack . symbolVal $ (Proxy :: Proxy sym)
184+ where
185+ str = pack . symbolVal $ (Proxy :: Proxy sym)
186+ arg = (str, typeFor lang (Proxy :: Proxy a))
145187
146−instance Elem JSON list => HasForeign (Delete list a) where
188+instance (Elem JSON list, HasForeignType lang a)
189+ => HasForeign lang (Delete list a) where
147190 type Foreign (Delete list a) = Req
148191
149− foreignFor Proxy req =
150− req & funcName %~ ("delete" :)
151− & reqMethod .~ "DELETE"
192+ foreignFor lang Proxy req =
193+ req & funcName %~ ("delete" :)
194+ & reqMethod .~ "DELETE"
195+ & reqReturnType .~ retType
196+ where
197+ retType = typeFor lang (Proxy :: Proxy a)
152198
153−instance Elem JSON list => HasForeign (Get list a) where
199+instance (Elem JSON list, HasForeignType lang a)
200+ => HasForeign lang (Get list a) where
154201 type Foreign (Get list a) = Req
155202
156− foreignFor Proxy req =
203+ foreignFor lang Proxy req =
157204 req & funcName %~ ("get" :)
158205 & reqMethod .~ "GET"
206+ & reqReturnType .~ retType
207+ where
208+ retType = typeFor lang (Proxy :: Proxy a)
159209
160−instance (KnownSymbol sym, HasForeign sublayout)
161− => HasForeign (Header sym a :> sublayout) where
210+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
211+ => HasForeign lang (Header sym a :> sublayout) where
162212 type Foreign (Header sym a :> sublayout) = Foreign sublayout
163213
164− foreignFor Proxy req =
165− foreignFor subP (req & reqHeaders <>~ [HeaderArg hname])
214+ foreignFor lang Proxy req =
215+ foreignFor lang subP $ req
216+ & reqHeaders <>~ [HeaderArg arg]
166217
167− where hname = pack . symbolVal $ (Proxy :: Proxy sym)
168− subP = Proxy :: Proxy sublayout
218+ where
219+ hname = pack . symbolVal $ (Proxy :: Proxy sym)
220+ arg = (hname, typeFor lang (Proxy :: Proxy a))
221+ subP = Proxy :: Proxy sublayout
169222
170−instance Elem JSON list => HasForeign (Post list a) where
223+instance (Elem JSON list, HasForeignType lang a)
224+ => HasForeign lang (Post list a) where
171225 type Foreign (Post list a) = Req
172226
173− foreignFor Proxy req =
227+ foreignFor lang Proxy req =
174228 req & funcName %~ ("post" :)
175229 & reqMethod .~ "POST"
230+ & reqReturnType .~ retType
231+ where
232+ retType = typeFor lang (Proxy :: Proxy a)
176233
177−instance Elem JSON list => HasForeign (Put list a) where
234+instance (Elem JSON list, HasForeignType lang a)
235+ => HasForeign lang (Put list a) where
178236 type Foreign (Put list a) = Req
179237
180− foreignFor Proxy req =
238+ foreignFor lang Proxy req =
181239 req & funcName %~ ("put" :)
182240 & reqMethod .~ "PUT"
241+ & reqReturnType .~ retType
242+ where
243+ retType = typeFor lang (Proxy :: Proxy a)
183244
184−instance (KnownSymbol sym, HasForeign sublayout)
185− => HasForeign (QueryParam sym a :> sublayout) where
245+instance (KnownSymbol sym, HasForeignType lang a, HasForeign lang sublayout)
246+ => HasForeign lang (QueryParam sym a :> sublayout) where
186247 type Foreign (QueryParam sym a :> sublayout) = Foreign sublayout
187248
188− foreignFor Proxy req =
189− foreignFor (Proxy :: Proxy sublayout) $
190− req & reqUrl.queryStr <>~ [QueryArg str Normal]
249+ foreignFor lang Proxy req =
250+ foreignFor lang (Proxy :: Proxy sublayout) $
251+ req & reqUrl.queryStr <>~ [QueryArg arg Normal]
191252
192− where str = pack . symbolVal $ (Proxy :: Proxy sym)
253+ where
254+ str = pack . symbolVal $ (Proxy :: Proxy sym)
255+ arg = (str, typeFor lang (Proxy :: Proxy a))
193256
194−instance (KnownSymbol sym, HasForeign sublayout)
195− => HasForeign (QueryParams sym a :> sublayout) where
257+instance (KnownSymbol sym, HasForeignType lang [a], HasForeign lang sublayout)
258+ => HasForeign lang (QueryParams sym a :> sublayout) where
196259 type Foreign (QueryParams sym a :> sublayout) = Foreign sublayout
197260
198− foreignFor Proxy req =
199− foreignFor (Proxy :: Proxy sublayout) $
200− req & reqUrl.queryStr <>~ [QueryArg str List]
261+ foreignFor lang Proxy req =
262+ foreignFor lang (Proxy :: Proxy sublayout) $
263+ req & reqUrl.queryStr <>~ [QueryArg arg List]
201264
202− where str = pack . symbolVal $ (Proxy :: Proxy sym)
265+ where
266+ str = pack . symbolVal $ (Proxy :: Proxy sym)
267+ arg = (str, typeFor lang (Proxy :: Proxy [a]))
203268
204−instance (KnownSymbol sym, HasForeign sublayout)
205− => HasForeign (QueryFlag sym :> sublayout) where
269+instance (KnownSymbol sym, HasForeignType lang a, a ~ Bool, HasForeign lang sublayout)
270+ => HasForeign lang (QueryFlag sym :> sublayout) where
206271 type Foreign (QueryFlag sym :> sublayout) = Foreign sublayout
207272
208− foreignFor Proxy req =
209− foreignFor (Proxy :: Proxy sublayout) $
210− req & reqUrl.queryStr <>~ [QueryArg str Flag]
273+ foreignFor lang Proxy req =
274+ foreignFor lang (Proxy :: Proxy sublayout) $
275+ req & reqUrl.queryStr <>~ [QueryArg arg Flag]
211276
212− where str = pack . symbolVal $ (Proxy :: Proxy sym)
277+ where
278+ str = pack . symbolVal $ (Proxy :: Proxy sym)
279+ arg = (str, typeFor lang (Proxy :: Proxy a))
213280
214−instance HasForeign Raw where
281+instance HasForeign lang Raw where
215282 type Foreign Raw = Method -> Req
216283
217− foreignFor Proxy req method =
284+ foreignFor _ Proxy req method =
218285 req & funcName %~ ((toLower method) :)
219286 & reqMethod .~ method
220287
221−instance (Elem JSON list, HasForeign sublayout) => HasForeign (ReqBody list a :> sublayout) where
288+instance (Elem JSON list, HasForeignType lang a, HasForeign lang sublayout)
289+ => HasForeign lang (ReqBody list a :> sublayout) where
222290 type Foreign (ReqBody list a :> sublayout) = Foreign sublayout
223291
224− foreignFor Proxy req =
225− foreignFor (Proxy :: Proxy sublayout) $
226− req & reqBody .~ True
292+ foreignFor lang Proxy req =
293+ foreignFor lang (Proxy :: Proxy sublayout) $
294+ req & reqBody .~ (Just $ typeFor lang (Proxy :: Proxy a))
227295
228−instance (KnownSymbol path, HasForeign sublayout)
229− => HasForeign (path :> sublayout) where
296+instance (KnownSymbol path, HasForeign lang sublayout)
297+ => HasForeign lang (path :> sublayout) where
230298 type Foreign (path :> sublayout) = Foreign sublayout
231299
232− foreignFor Proxy req =
233− foreignFor (Proxy :: Proxy sublayout) $
300+ foreignFor lang Proxy req =
301+ foreignFor lang (Proxy :: Proxy sublayout) $
234302 req & reqUrl.path <>~ [Segment (Static str)]
235303 & funcName %~ (++ [str])
236304
237− where str = Data.Text.map (\c -> if c == '.' then '_' else c) . pack . symbolVal $ (Proxy :: Proxy path)
305+ where
306+ str = Data.Text.map (\c -> if c == '.' then '_' else c)
307+ . pack . symbolVal $ (Proxy :: Proxy path)
238308
239−instance HasForeign sublayout => HasForeign (RemoteHost :> sublayout) where
309+instance HasForeign lang sublayout => HasForeign lang (RemoteHost :> sublayout) where
240310 type Foreign (RemoteHost :> sublayout) = Foreign sublayout
241311
242− foreignFor Proxy req =
243− foreignFor (Proxy :: Proxy sublayout) req
312+ foreignFor lang Proxy req =
313+ foreignFor lang (Proxy :: Proxy sublayout) req
244314
245−instance HasForeign sublayout => HasForeign (IsSecure :> sublayout) where
315+instance HasForeign lang sublayout => HasForeign lang (IsSecure :> sublayout) where
246316 type Foreign (IsSecure :> sublayout) = Foreign sublayout
247317
248− foreignFor Proxy req =
249− foreignFor (Proxy :: Proxy sublayout) req
318+ foreignFor lang Proxy req =
319+ foreignFor lang (Proxy :: Proxy sublayout) req
250320
251−instance HasForeign sublayout => HasForeign (Vault :> sublayout) where
321+instance HasForeign lang sublayout => HasForeign lang (Vault :> sublayout) where
252322 type Foreign (Vault :> sublayout) = Foreign sublayout
253323
254− foreignFor Proxy req =
255− foreignFor (Proxy :: Proxy sublayout) req
324+ foreignFor lang Proxy req =
325+ foreignFor lang (Proxy :: Proxy sublayout) req
256326
257−instance HasForeign sublayout => HasForeign (HttpVersion :> sublayout) where
327+instance HasForeign lang sublayout => HasForeign lang (HttpVersion :> sublayout) where
258328 type Foreign (HttpVersion :> sublayout) = Foreign sublayout
259329
260− foreignFor Proxy req =
261− foreignFor (Proxy :: Proxy sublayout) req
330+ foreignFor lang Proxy req =
331+ foreignFor lang (Proxy :: Proxy sublayout) req
332+
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]
338+
339+instance GenerateList Req where
340+ generateList r = [r]
341+
342+instance (GenerateList start, GenerateList rest) => GenerateList (start :<|> rest) where
343+ generateList (start :<|> rest) = (generateList start) ++ (generateList rest)
344+
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)
349+