servant-elm 0.4.0.1 → 0.5.0.0
raw patch · 9 files changed
+123/−36 lines, 9 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Servant.Elm: data Proxy k (t :: k) :: forall k. k -> *
+ Servant.Elm: data Proxy k (t :: k) :: forall k. () => k -> *
+ Servant.Elm.Internal.Generate: elmListOfMaybes :: [Doc] -> Doc
+ Servant.Elm.Internal.Generate: isElmMaybeStringType :: ElmOptions -> ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isElmMaybeType :: ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isNotCookie :: HeaderArg f -> Bool
+ Servant.Elm.Internal.Orphans: instance Elm.Type.ElmType a => Elm.Type.ElmType (Servant.API.ResponseHeaders.Headers ls a)
Files
- CHANGELOG.md +8/−0
- servant-elm.cabal +1/−1
- src/Servant/Elm/Internal/Generate.hs +51/−23
- src/Servant/Elm/Internal/Orphans.hs +8/−1
- test/Common.hs +11/−4
- test/GenerateSpec.hs +4/−0
- test/elm-sources/getBooksSource.elm +5/−2
- test/elm-sources/getWithaheaderSource.elm +8/−5
- test/elm-sources/getWitharesponseheaderSource.elm +27/−0
CHANGELOG.md view
@@ -1,3 +1,11 @@+0.5.0.0+-------+* Fix generation for APIs with response headers.+* Support Servant's `Optional` and `Required` modifiers for+ `Header`s and `QueryParam`s (`Header` arguments are now `Maybe`s+ by default). (phaazon) (#31)+* Filter out forbidden Cookie header. (xilnocas) (#37)+ 0.4.0.1 ------- * Remove hyphens from generated function names. (servant-foreign-0.10 no longer
servant-elm.cabal view
@@ -1,5 +1,5 @@ name: servant-elm-version: 0.4.0.1+version: 0.5.0.0 synopsis: Automatically derive Elm functions to query servant webservices. description: Please see README.md homepage: http://github.com/mattjbray/servant-elm#readme
src/Servant/Elm/Internal/Generate.hs view
@@ -12,7 +12,7 @@ import qualified Data.Text as T import qualified Data.Text.Lazy as L import qualified Data.Text.Encoding as T-import Elm (ElmDatatype)+import Elm (ElmDatatype(..), ElmPrimitive(..)) import qualified Elm import Servant.API (NoContent (..)) import Servant.Elm.Internal.Foreign (LangElm, getEndpoints)@@ -198,6 +198,7 @@ headerTypes = [ header ^. F.headerArg . F.argType . to elmTypeRef | header <- request ^. F.reqHeaders+ , isNotCookie header ] urlCaptureTypes :: [Doc]@@ -209,15 +210,8 @@ queryTypes :: [Doc] queryTypes =- [ arg ^. F.queryArgName . F.argType . to (elmTypeRef . wrapper)+ [ arg ^. F.queryArgName . F.argType . to elmTypeRef | arg <- request ^. F.reqUrl . F.queryStr- , wrapper <- [- case arg ^. F.queryArgType of- F.Normal ->- Elm.ElmPrimitive . Elm.EMaybe- _ ->- id- ] ] bodyType :: Maybe Doc@@ -233,7 +227,7 @@ elmHeaderArg :: F.HeaderArg ElmDatatype -> Doc elmHeaderArg header = "header_" <>- header ^. F.headerArg . F.argName . to (stext . F.unPathSegment)+ header ^. F.headerArg . F.argName . to (stext . T.replace "-" "_" . F.unPathSegment) elmCaptureArg :: F.Segment ElmDatatype -> Doc@@ -253,6 +247,14 @@ "body" +isNotCookie :: F.HeaderArg f -> Bool+isNotCookie header =+ header+ ^. F.headerArg+ . F.argName+ . to ((/= "cookie") . T.toLower . F.unPathSegment)++ mkArgs :: ElmOptions -> F.Req ElmDatatype@@ -266,6 +268,7 @@ , -- Headers [ elmHeaderArg header | header <- request ^. F.reqHeaders+ , isNotCookie header ] , -- URL Captures [ elmCaptureArg segment@@ -299,14 +302,16 @@ case qarg ^. F.queryArgType of F.Normal -> let+ argType = qarg ^. F.queryArgName . F.argType+ wrapped = isElmMaybeType argType -- Don't use "toString" on Elm Strings, otherwise we get extraneous quotes. toStringSrc =- if isElmStringType opts (qarg ^. F.queryArgName . F.argType) then+ if isElmStringType opts argType || isElmMaybeStringType opts argType then "" else "toString >> " in- name <$>+ (if wrapped then name else "Just" <+> name) <$> indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Http.encodeUri >> (++)" <+> dquotes (elmName <> equals)) <$> "|> Maybe.withDefault" <+> dquotes empty) @@ -334,7 +339,7 @@ indent i (dquotes method) , "headers =" <$> indent i- (elmList headers)+ (elmListOfMaybes headers) , "url =" <$> indent i url , "body =" <$>@@ -350,17 +355,26 @@ method = request ^. F.reqMethod . to (stext . T.decodeUtf8) + mkHeader header =+ let headerName = header ^. F.headerArg . F.argName . to (stext . F.unPathSegment)+ headerArgName = elmHeaderArg header+ argType = header ^. F.headerArg . F.argType+ wrapped = isElmMaybeType argType+ toStringSrc =+ if isElmMaybeStringType opts argType || isElmStringType opts argType then+ mempty+ else+ " << toString"+ in+ "Maybe.map" <+> parens (("Http.header" <+> dquotes headerName <> toStringSrc))+ <+>+ (if wrapped then headerArgName else parens ("Just" <+> headerArgName))+ headers =- [("Http.header" <+> dquotes headerName <+>- (if isElmStringType opts (header ^. F.headerArg . F.argType) then- headerArgName- else- parens ("toString " <> headerArgName)- ))- | header <- request ^. F.reqHeaders- , headerName <- [header ^. F.headerArg . F.argName . to (stext . F.unPathSegment)]- , headerArgName <- [elmHeaderArg header]- ]+ [ mkHeader header+ | header <- request ^. F.reqHeaders+ , isNotCookie header+ ] url = mkUrl opts (request ^. F.reqUrl . F.path)@@ -454,7 +468,17 @@ isElmStringType opts elmTypeExpr = elmTypeExpr `elem` stringElmTypes opts +{- | Determines whether a type is 'Maybe a' where 'a' is something akin to a 'String'.+-}+isElmMaybeStringType :: ElmOptions -> ElmDatatype -> Bool+isElmMaybeStringType opts (ElmPrimitive (EMaybe elmTypeExpr)) = elmTypeExpr `elem` stringElmTypes opts+isElmMaybeStringType _ _ = False +isElmMaybeType :: ElmDatatype -> Bool+isElmMaybeType (ElmPrimitive (EMaybe _)) = True+isElmMaybeType _ = False++ -- Doc helpers @@ -471,3 +495,7 @@ elmList :: [Doc] -> Doc elmList [] = lbracket <> rbracket elmList ds = lbracket <+> hsep (punctuate (line <> comma) ds) <$> rbracket++elmListOfMaybes :: [Doc] -> Doc+elmListOfMaybes [] = lbracket <> rbracket+elmListOfMaybes ds = "List.filterMap identity" <$> indent 4 (elmList ds)
src/Servant/Elm/Internal/Orphans.hs view
@@ -1,10 +1,11 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE StandaloneDeriving #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module Servant.Elm.Internal.Orphans where import Elm (ElmDatatype, ElmType, toElmType)-import Servant.API (NoContent)+import Servant.API (NoContent, Headers, getResponse) instance ElmType ElmDatatype where@@ -12,3 +13,9 @@ instance ElmType NoContent+++-- TODO: Generate Elm functions that can handle the response headers. PRs+-- welcome!+instance (ElmType a) => ElmType (Headers ls a) where+ toElmType = toElmType . getResponse
test/Common.hs view
@@ -3,14 +3,15 @@ {-# LANGUAGE TypeOperators #-} module Common where -import Data.Text (Text) import Data.Aeson (ToJSON)-import Data.Proxy (Proxy(Proxy))+import Data.Proxy (Proxy (Proxy))+import Data.Text (Text) import Elm (ElmType) import GHC.Generics (Generic) import Servant.API ((:<|>), (:>), Capture, Get, GetNoContent, Header,- JSON, NoContent, Post, PostNoContent, Put,- QueryFlag, QueryParam, QueryParams, ReqBody)+ Header', Headers, JSON, NoContent, Post,+ PostNoContent, Put, QueryFlag, QueryParam,+ QueryParam', QueryParams, ReqBody, Required) data Book = Book { title :: String@@ -35,6 +36,7 @@ :> QueryFlag "published" :> QueryParam "sort" String :> QueryParam "year" Int+ :> QueryParam' '[Required] "category" String :> QueryParams "filters" (Maybe Bool) :> Get '[JSON] [Book] :<|> "books"@@ -45,9 +47,14 @@ :<|> "nothing" :> Put '[JSON] () -- old way to specify no content :<|> "with-a-header"+ :> Header "Cookie" String :> Header "myStringHeader" String :> Header "MyIntHeader" Int+ :> Header' '[Required] "MyRequiredStringHeader" String+ :> Header' '[Required] "MyRequiredIntHeader" Int :> Get '[JSON] String+ :<|> "with-a-response-header"+ :> Get '[JSON] (Headers '[Header "myResponse" String] String) testApi :: Proxy TestApi testApi = Proxy
test/GenerateSpec.hs view
@@ -63,6 +63,10 @@ , ( "test/elm-sources/getWithaheaderSource.elm" , "module GetWithAHeaderSource exposing (..)\n\n" <> "import Http\n" <>+ "import Json.Decode exposing (..)\n\n\n")+ , ( "test/elm-sources/getWitharesponseheaderSource.elm"+ , "module GetWithAResponseHeaderSource exposing (..)\n\n" <>+ "import Http\n" <> "import Json.Decode exposing (..)\n\n\n")] let generated = map (<> "\n") (generateElmForAPI testApi) generated `itemsShouldBe` expected
test/elm-sources/getBooksSource.elm view
@@ -4,8 +4,8 @@ import Json.Decode exposing (..) -getBooks : Bool -> Maybe (String) -> Maybe (Int) -> List (Maybe (Bool)) -> Http.Request (List (Book))-getBooks query_published query_sort query_year query_filters =+getBooks : Bool -> Maybe (String) -> Maybe (Int) -> String -> List (Maybe (Bool)) -> Http.Request (List (Book))+getBooks query_published query_sort query_year query_category query_filters = let params = List.filter (not << String.isEmpty)@@ -18,6 +18,9 @@ |> Maybe.withDefault "" , query_year |> Maybe.map (toString >> Http.encodeUri >> (++) "year=")+ |> Maybe.withDefault ""+ , Just query_category+ |> Maybe.map (Http.encodeUri >> (++) "category=") |> Maybe.withDefault "" , query_filters |> List.map (\val -> "query_filters[]=" ++ (val |> toString |> Http.encodeUri))
test/elm-sources/getWithaheaderSource.elm view
@@ -4,15 +4,18 @@ import Json.Decode exposing (..) -getWithaheader : String -> Int -> Http.Request (String)-getWithaheader header_myStringHeader header_MyIntHeader =+getWithaheader : Maybe (String) -> Maybe (Int) -> String -> Int -> Http.Request (String)+getWithaheader header_myStringHeader header_MyIntHeader header_MyRequiredStringHeader header_MyRequiredIntHeader = Http.request { method = "GET" , headers =- [ Http.header "myStringHeader" header_myStringHeader- , Http.header "MyIntHeader" (toString header_MyIntHeader)- ]+ List.filterMap identity+ [ Maybe.map (Http.header "myStringHeader") header_myStringHeader+ , Maybe.map (Http.header "MyIntHeader" << toString) header_MyIntHeader+ , Maybe.map (Http.header "MyRequiredStringHeader") (Just header_MyRequiredStringHeader)+ , Maybe.map (Http.header "MyRequiredIntHeader" << toString) (Just header_MyRequiredIntHeader)+ ] , url = String.join "/" [ ""
+ test/elm-sources/getWitharesponseheaderSource.elm view
@@ -0,0 +1,27 @@+module GetWithAResponseHeaderSource exposing (..)++import Http+import Json.Decode exposing (..)+++getWitharesponseheader : Http.Request (String)+getWitharesponseheader =+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ String.join "/"+ [ ""+ , "with-a-response-header"+ ]+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson string+ , timeout =+ Nothing+ , withCredentials =+ False+ }