git.iko.soy/typeable/servant
on master help sign in
doc(servant-foreign): Document module
e486564Philip Patsch
servant-foreign/src/Servant/Foreign.hs+14−12
@@ -3,28 +3,30 @@
3--
3--
4-- See documentation of 'HasForeignType' for a simple example. 'listFromAPI' returns a list of all your endpoints and their foreign types, given a mapping from Haskell types to foreign types (conventionally called `ftypes` below).
4-- See documentation of 'HasForeignType' for a simple example. 'listFromAPI' returns a list of all your endpoints and their foreign types, given a mapping from Haskell types to foreign types (conventionally called `ftypes` below).
5module Servant.Foreign
5module Servant.Foreign
6+ (
7+ -- * Main API
6− ( listFromAPI
8+ listFromAPI
7 , Req(..)
9 , Req(..)
8 , defReq
10 , defReq
9 , HasForeignType(..)
11 , HasForeignType(..)
10 , GenerateList(..)
12 , GenerateList(..)
11 , HasForeign(..)
13 , HasForeign(..)
12 , NoTypes
14 , NoTypes
13− , ArgType(..)
15+ -- * Subtypes of 'Req'
14− , HeaderArg(..)
16+ , Url(..)
15− , QueryArg(..)
17+ , Path
16− , ReqBodyContentType(..)
17 , Segment(..)
18 , Segment(..)
19+ , SegmentType(..)
18 , isCapture
20 , isCapture
19 , captureArg
21 , captureArg
20− , SegmentType(..)
22+ , QueryArg(..)
21− , Url(..)
23+ , ArgType(..)
22− -- * aliases
24+ , HeaderArg(..)
23− , Path
24 , Arg(..)
25 , Arg(..)
25 , FunctionName(..)
26 , FunctionName(..)
27+ , ReqBodyContentType(..)
26 , PathSegment(..)
28 , PathSegment(..)
27− -- * lenses
29+ -- * Lenses
28 , argName
30 , argName
29 , argType
31 , argType
30 , argPath
32 , argPath
@@ -40,7 +42,7 @@ module Servant.Foreign
40 , queryArgName
42 , queryArgName
41 , queryArgType
43 , queryArgType
42 , headerArg
44 , headerArg
43− -- * prisms
45+ -- * Prisms
44 , _PathSegment
46 , _PathSegment
45 , _HeaderArg
47 , _HeaderArg
46 , _ReplaceHeaderArg
48 , _ReplaceHeaderArg
@@ -49,7 +51,7 @@ module Servant.Foreign
49 , _Normal
51 , _Normal
50 , _Flag
52 , _Flag
51 , _List
53 , _List
52− -- * re-exports
54+ -- * Re-exports
53 , module Servant.API
55 , module Servant.API
54 , module Servant.Foreign.Inflections
56 , module Servant.Foreign.Inflections
55 ) where
57 ) where
servant-foreign/src/Servant/Foreign/Internal.hs+119−27
@@ -46,45 +46,67 @@ newtype FunctionName = FunctionName { unFunctionName :: [Text] }
46
46
47makePrisms ''FunctionName
47makePrisms ''FunctionName
48
48
49+-- | See documentation of 'Arg'
49newtype PathSegment = PathSegment { unPathSegment :: Text }
50newtype PathSegment = PathSegment { unPathSegment :: Text }
50 deriving (Data, Show, Eq, IsString, Semigroup, Monoid, Typeable)
51 deriving (Data, Show, Eq, IsString, Semigroup, Monoid, Typeable)
51
52
52makePrisms ''PathSegment
53makePrisms ''PathSegment
53
54
55+-- | Maps a name to the foreign type that belongs to the annotated value.
56+--
57+-- Used for header args, query args, and capture args.
54−data Arg f = Arg
58+data Arg ftype = Arg
55 { _argName :: PathSegment
59 { _argName :: PathSegment
60+ -- ^ The name to be captured.
61+ --
62+ -- Only for capture args it really denotes a path segment.
56− , _argType :: f }
63+ , _argType :: ftype
64+ -- ^ Foreign type the associated value will have
65+ }
57 deriving (Data, Eq, Show, Typeable)
66 deriving (Data, Eq, Show, Typeable)
58
67
59makeLenses ''Arg
68makeLenses ''Arg
60
69
61−argPath :: Getter (Arg f) Text
70+argPath :: Getter (Arg ftype) Text
62argPath = argName . _PathSegment
71argPath = argName . _PathSegment
63
72
64−data SegmentType f
73+data SegmentType ftype
65 = Static PathSegment
74 = Static PathSegment
66− -- ^ a static path segment. like "/foo"
75+ -- ^ Static path segment.
76+ --
77+ -- @"foo\/bar\/baz"@
78+ --
79+ -- contains the static segments @"foo"@, @"bar"@ and @"baz"@.
67− | Cap (Arg f)
80+ | Cap (Arg ftype)
68− -- ^ a capture. like "/:userid"
81+ -- ^ A capture.
82+ --
83+ -- @"user\/{userid}\/name"@
84+ --
85+ -- would capture the arg @userid@ with type @ftype@.
69 deriving (Data, Eq, Show, Typeable)
86 deriving (Data, Eq, Show, Typeable)
70
87
71makePrisms ''SegmentType
88makePrisms ''SegmentType
72
89
90+-- | A part of the Url’s path.
73−newtype Segment f = Segment { unSegment :: SegmentType f }
91+newtype Segment ftype = Segment { unSegment :: SegmentType ftype }
74 deriving (Data, Eq, Show, Typeable)
92 deriving (Data, Eq, Show, Typeable)
75
93
76makePrisms ''Segment
94makePrisms ''Segment
77
95
96+-- | Whether a segment is a 'Cap'.
78−isCapture :: Segment f -> Bool
97+isCapture :: Segment ftype -> Bool
79isCapture (Segment (Cap _)) = True
98isCapture (Segment (Cap _)) = True
80isCapture _ = False
99isCapture _ = False
81
100
101+-- | Crashing Arg extraction from segment, TODO: remove
82−captureArg :: Segment f -> Arg f
102+captureArg :: Segment ftype -> Arg ftype
83captureArg (Segment (Cap s)) = s
103captureArg (Segment (Cap s)) = s
84captureArg _ = error "captureArg called on non capture"
104captureArg _ = error "captureArg called on non capture"
85
105
106+-- TODO: remove, unnecessary indirection
86−type Path f = [Segment f]
107+type Path ftype = [Segment ftype]
87
108
109+-- | Type of a 'QueryArg'.
88data ArgType
110data ArgType
89 = Normal
111 = Normal
90 | Flag
112 | Flag
@@ -93,18 +115,41 @@ data ArgType
93
115
94makePrisms ''ArgType
116makePrisms ''ArgType
95
117
118+-- | Url Query argument.
119+--
120+-- Urls can contain query arguments, which is a list of key-value pairs.
121+-- In a typical url, query arguments look like this:
122+--
123+-- @?foo=bar&alist[]=el1&alist[]=el2&aflag@
124+--
125+-- Each pair can be
126+--
127+-- * @?foo=bar@: a plain key-val pair, either optional or required ('QueryParam')
128+-- * @?aflag@: a flag (no value, implicitly Bool with default `false` if it’s missing) ('QueryFlag')
129+-- * @?alist[]=el1&alist[]=el2@: list of values ('QueryParams')
130+--
131+-- @_queryArgType@ will be set accordingly.
132+--
133+-- For the plain key-val pairs ('QueryParam'), @_queryArgName@’s @ftype@ will be wrapped in a @Maybe@ if the argument is optional.
96−data QueryArg f = QueryArg
134+data QueryArg ftype = QueryArg
97− { _queryArgName :: Arg f
135+ { _queryArgName :: Arg ftype
136+ -- ^ Name and foreign type of the argument. Will be wrapped in `Maybe` if the query is optional and in a `[]` if the query is a list
98 , _queryArgType :: ArgType
137 , _queryArgType :: ArgType
138+ -- ^ one of normal/plain, list or flag
99 }
139 }
100 deriving (Data, Eq, Show, Typeable)
140 deriving (Data, Eq, Show, Typeable)
101
141
102makeLenses ''QueryArg
142makeLenses ''QueryArg
103
143
104−data HeaderArg f = HeaderArg
144+data HeaderArg ftype =
145+ -- | The name of the header and the foreign type of its value.
146+ HeaderArg
105− { _headerArg :: Arg f }
147+ { _headerArg :: Arg ftype }
148+ -- | Unused, will never be set.
149+ --
150+ -- TODO: remove
106 | ReplaceHeaderArg
151 | ReplaceHeaderArg
107− { _headerArg :: Arg f
152+ { _headerArg :: Arg ftype
108 , _headerPattern :: Text
153 , _headerPattern :: Text
109 }
154 }
110 deriving (Data, Eq, Show, Typeable)
155 deriving (Data, Eq, Show, Typeable)
@@ -113,29 +158,71 @@ makeLenses ''HeaderArg
113
158
114makePrisms ''HeaderArg
159makePrisms ''HeaderArg
115
160
161+-- | Full endpoint url, with all captures and parameters
116−data Url f = Url
162+data Url ftype = Url
117− { _path :: Path f
163+ { _path :: Path ftype
164+ -- ^ Url path, list of either static segments or captures
165+ --
166+ -- @"foo\/{id}\/bar"@
118− , _queryStr :: [QueryArg f]
167+ , _queryStr :: [QueryArg ftype]
168+ -- ^ List of query args
169+ --
170+ -- @"?foo=bar&a=b"@
119− , _frag :: Maybe f
171+ , _frag :: Maybe ftype
172+ -- ^ Url fragment.
173+ --
174+ -- Not sent to the HTTP server, so only useful for frontend matters (e.g. inter-page linking).
175+ --
176+ -- @#fragmentText@
120 }
177 }
121 deriving (Data, Eq, Show, Typeable)
178 deriving (Data, Eq, Show, Typeable)
122
179
123−defUrl :: Url f
180+defUrl :: Url ftype
124defUrl = Url [] [] Nothing
181defUrl = Url [] [] Nothing
125
182
126makeLenses ''Url
183makeLenses ''Url
127
184
185+-- | See documentation of '_reqBodyContentType'
128data ReqBodyContentType = ReqBodyJSON | ReqBodyMultipart
186data ReqBodyContentType = ReqBodyJSON | ReqBodyMultipart
129 deriving (Data, Eq, Show, Read)
187 deriving (Data, Eq, Show, Read)
130
188
189+-- | Full description of an endpoint in your API, generated by 'listFromAPI'. It should give you all the information needed to generate foreign language bindings.
190+--
191+-- Every field containing @ftype@ will use the foreign type mapping specified via 'HasForeignType' (see its docstring on how to set that up).
192+--
193+-- See https://docs.servant.dev/en/stable/tutorial/ApiType.html for accessible documentation of the possible content of an endpoint.
131−data Req f = Req
194+data Req ftype = Req
132− { _reqUrl :: Url f
195+ { _reqUrl :: Url ftype
196+ -- ^ Full list of URL segments, including captures
133 , _reqMethod :: HTTP.Method
197 , _reqMethod :: HTTP.Method
198+ -- ^ @\"GET\"@\/@\"POST\"@\/@\"PUT\"@\/…
134− , _reqHeaders :: [HeaderArg f]
199+ , _reqHeaders :: [HeaderArg ftype]
200+ -- ^ Headers required by this endpoint, with their type
135− , _reqBody :: Maybe f
201+ , _reqBody :: Maybe ftype
202+ -- ^ Foreign type of the expected request body ('ReqBody'), if any
136− , _reqReturnType :: Maybe f
203+ , _reqReturnType :: Maybe ftype
204+ -- ^ The foreign type of the response, if any
137 , _reqFuncName :: FunctionName
205 , _reqFuncName :: FunctionName
206+ -- ^ The URL segments rendered in a way that they can be easily concatenated into a canonical function name
138 , _reqBodyContentType :: ReqBodyContentType
207 , _reqBodyContentType :: ReqBodyContentType
208+ -- ^ The content type the request body is transferred as.
209+ --
210+ -- This is a severe limitation of @servant-foreign@ currently,
211+ -- as we only allow the content type to be `JSON`
212+ -- no user-defined content types. ('ReqBodyMultipart' is not
213+ -- actually implemented.)
214+ --
215+ -- Thus, any routes looking like this will work:
216+ --
217+ -- @"foo" :> Get '[JSON] Foo@
218+ --
219+ -- while routes like
220+ --
221+ -- @"foo" :> Get '[MyFancyContentType] Foo@
222+ --
223+ -- will fail with an error like
224+ --
225+ -- @• JSON expected in list '[MyFancyContentType]@
139 }
226 }
140 deriving (Data, Eq, Show, Typeable)
227 deriving (Data, Eq, Show, Typeable)
141
228
@@ -179,11 +266,16 @@ defReq = Req defUrl "GET" [] Nothing Nothing (FunctionName []) ReqBodyJSON
179class HasForeignType lang ftype a where
266class HasForeignType lang ftype a where
180 typeFor :: Proxy lang -> Proxy ftype -> Proxy a -> ftype
267 typeFor :: Proxy lang -> Proxy ftype -> Proxy a -> ftype
181
268
269+-- | The language definition without any foreign types. It can be used for dynamic languages which do not /do/ type annotations.
182data NoTypes
270data NoTypes
183
271
272+-- | Use if the foreign language does not have any types.
184−instance HasForeignType NoTypes NoContent ftype where
273+instance HasForeignType NoTypes NoContent a where
185 typeFor _ _ _ = NoContent
274 typeFor _ _ _ = NoContent
186
275
276+-- | Implementation of the Servant framework types.
277+--
278+-- Relevant instances: Everything containing 'HasForeignType'.
187class HasForeign lang ftype (api :: *) where
279class HasForeign lang ftype (api :: *) where
188 type Foreign ftype api :: *
280 type Foreign ftype api :: *
189 foreignFor :: Proxy lang -> Proxy ftype -> Proxy api -> Req ftype -> Foreign ftype api
281 foreignFor :: Proxy lang -> Proxy ftype -> Proxy api -> Req ftype -> Foreign ftype api