servant-elm 0.5.0.0 → 0.6.0.0
raw patch · 28 files changed
+965/−650 lines, 28 filesdep +elm-bridgedep +servant-clientdep +typed-processdep −elm-exportnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: elm-bridge, servant-client, typed-process
Dependencies removed: elm-export
API changes (from Hackage documentation)
- Servant.Elm: Spec :: [Text] -> [Text] -> Spec
- Servant.Elm: [elmExportOptions] :: ElmOptions -> Options
- Servant.Elm: class ElmType a
- Servant.Elm: data Spec :: *
- Servant.Elm: specsToDir :: [Spec] -> FilePath -> IO ()
- Servant.Elm.Internal.Foreign: instance Elm.Type.ElmType a => Servant.Foreign.Internal.HasForeignType Servant.Elm.Internal.Foreign.LangElm Elm.Type.ElmDatatype a
- Servant.Elm.Internal.Generate: [elmExportOptions] :: ElmOptions -> Options
- Servant.Elm.Internal.Orphans: instance Elm.Type.ElmType Elm.Type.ElmDatatype
- Servant.Elm.Internal.Orphans: instance Elm.Type.ElmType Servant.API.ContentTypes.NoContent
- Servant.Elm.Internal.Orphans: instance Elm.Type.ElmType a => Elm.Type.ElmType (Servant.API.ResponseHeaders.Headers ls a)
+ Servant.Elm: ETyApp :: EType -> EType -> EType
+ Servant.Elm: ETyCon :: ETCon -> EType
+ Servant.Elm: ETyTuple :: Int -> EType
+ Servant.Elm: ETyVar :: ETVar -> EType
+ Servant.Elm: [DefineElm] :: forall a. IsElmDefinition a => Proxy a -> DefineElm
+ Servant.Elm: [elmAlterations] :: ElmOptions -> ETypeDef -> ETypeDef
+ Servant.Elm: [elmTypeAlterations] :: ElmOptions -> EType -> EType
+ Servant.Elm: data DefineElm
+ Servant.Elm: data EType
+ Servant.Elm: defaultOptions :: Options
+ Servant.Elm: defaultTypeAlterations :: EType -> EType
+ Servant.Elm: deriveBoth :: Options -> Name -> Q [Dec]
+ Servant.Elm: deriveElmDef :: Options -> Name -> Q [Dec]
+ Servant.Elm: generateElmModule :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => Namespace -> Text -> FilePath -> [DefineElm] -> Proxy api -> IO ()
+ Servant.Elm: generateElmModuleWith :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => ElmOptions -> Namespace -> Text -> FilePath -> [DefineElm] -> Proxy api -> IO ()
+ Servant.Elm: toElmType :: Typeable a => Proxy a -> EType
+ Servant.Elm.Internal.Foreign: instance Data.Typeable.Internal.Typeable a => Servant.Foreign.Internal.HasForeignType Servant.Elm.Internal.Foreign.LangElm Elm.TyRep.EType (Servant.API.ResponseHeaders.Headers b a)
+ Servant.Elm.Internal.Foreign: instance Data.Typeable.Internal.Typeable a => Servant.Foreign.Internal.HasForeignType Servant.Elm.Internal.Foreign.LangElm Elm.TyRep.EType a
+ Servant.Elm.Internal.Generate: [elmAlterations] :: ElmOptions -> ETypeDef -> ETypeDef
+ Servant.Elm.Internal.Generate: [elmTypeAlterations] :: ElmOptions -> EType -> EType
+ Servant.Elm.Internal.Generate: generateElmModule :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => Namespace -> Text -> FilePath -> [DefineElm] -> Proxy api -> IO ()
+ Servant.Elm.Internal.Generate: generateElmModuleWith :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => ElmOptions -> Namespace -> Text -> FilePath -> [DefineElm] -> Proxy api -> IO ()
+ Servant.Elm.Internal.Generate: isElmListOfMaybeBoolType :: EType -> Bool
+ Servant.Elm.Internal.Generate: renderDecoderName :: EType -> Doc
+ Servant.Elm.Internal.Generate: toElmDecoderRefWith :: ElmOptions -> EType -> Text
+ Servant.Elm.Internal.Generate: toElmEncoderRefWith :: ElmOptions -> EType -> Text
+ Servant.Elm.Internal.Generate: toElmTypeRefWith :: ElmOptions -> EType -> Text
+ Servant.Elm.Internal.Generate: type Namespace = [String]
+ Servant.Elm.Internal.Options: defaultOptions :: Options
- Servant.Elm: ElmOptions :: UrlPrefix -> Options -> [ElmDatatype] -> [ElmDatatype] -> ElmOptions
+ Servant.Elm: ElmOptions :: UrlPrefix -> (EType -> EType) -> (ETypeDef -> ETypeDef) -> [EType] -> [EType] -> ElmOptions
- Servant.Elm: Proxy :: Proxy k
+ Servant.Elm: Proxy :: Proxy
- Servant.Elm: [emptyResponseElmTypes] :: ElmOptions -> [ElmDatatype]
+ Servant.Elm: [emptyResponseElmTypes] :: ElmOptions -> [EType]
- Servant.Elm: [stringElmTypes] :: ElmOptions -> [ElmDatatype]
+ Servant.Elm: [stringElmTypes] :: ElmOptions -> [EType]
- Servant.Elm: data Proxy k (t :: k) :: forall k. () => k -> *
+ Servant.Elm: data Proxy (t :: k) :: forall k. () => k -> Type
- Servant.Elm: generateElmForAPI :: (HasForeign LangElm ElmDatatype api, GenerateList ElmDatatype (Foreign ElmDatatype api)) => Proxy api -> [Text]
+ Servant.Elm: generateElmForAPI :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => Proxy api -> [Text]
- Servant.Elm: generateElmForAPIWith :: (HasForeign LangElm ElmDatatype api, GenerateList ElmDatatype (Foreign ElmDatatype api)) => ElmOptions -> Proxy api -> [Text]
+ Servant.Elm: generateElmForAPIWith :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => ElmOptions -> Proxy api -> [Text]
- Servant.Elm.Internal.Foreign: getEndpoints :: (HasForeign LangElm ElmDatatype api, GenerateList ElmDatatype (Foreign ElmDatatype api)) => Proxy api -> [Req ElmDatatype]
+ Servant.Elm.Internal.Foreign: getEndpoints :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => Proxy api -> [Req EType]
- Servant.Elm.Internal.Generate: ElmOptions :: UrlPrefix -> Options -> [ElmDatatype] -> [ElmDatatype] -> ElmOptions
+ Servant.Elm.Internal.Generate: ElmOptions :: UrlPrefix -> (EType -> EType) -> (ETypeDef -> ETypeDef) -> [EType] -> [EType] -> ElmOptions
- Servant.Elm.Internal.Generate: [emptyResponseElmTypes] :: ElmOptions -> [ElmDatatype]
+ Servant.Elm.Internal.Generate: [emptyResponseElmTypes] :: ElmOptions -> [EType]
- Servant.Elm.Internal.Generate: [stringElmTypes] :: ElmOptions -> [ElmDatatype]
+ Servant.Elm.Internal.Generate: [stringElmTypes] :: ElmOptions -> [EType]
- Servant.Elm.Internal.Generate: elmCaptureArg :: Segment ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: elmCaptureArg :: Segment EType -> Doc
- Servant.Elm.Internal.Generate: elmHeaderArg :: HeaderArg ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: elmHeaderArg :: HeaderArg EType -> Doc
- Servant.Elm.Internal.Generate: elmQueryArg :: QueryArg ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: elmQueryArg :: QueryArg EType -> Doc
- Servant.Elm.Internal.Generate: generateElmForAPI :: (HasForeign LangElm ElmDatatype api, GenerateList ElmDatatype (Foreign ElmDatatype api)) => Proxy api -> [Text]
+ Servant.Elm.Internal.Generate: generateElmForAPI :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => Proxy api -> [Text]
- Servant.Elm.Internal.Generate: generateElmForAPIWith :: (HasForeign LangElm ElmDatatype api, GenerateList ElmDatatype (Foreign ElmDatatype api)) => ElmOptions -> Proxy api -> [Text]
+ Servant.Elm.Internal.Generate: generateElmForAPIWith :: (HasForeign LangElm EType api, GenerateList EType (Foreign EType api)) => ElmOptions -> Proxy api -> [Text]
- Servant.Elm.Internal.Generate: generateElmForRequest :: ElmOptions -> Req ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: generateElmForRequest :: ElmOptions -> Req EType -> Doc
- Servant.Elm.Internal.Generate: isElmMaybeStringType :: ElmOptions -> ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isElmMaybeStringType :: ElmOptions -> EType -> Bool
- Servant.Elm.Internal.Generate: isElmMaybeType :: ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isElmMaybeType :: EType -> Bool
- Servant.Elm.Internal.Generate: isElmStringType :: ElmOptions -> ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isElmStringType :: ElmOptions -> EType -> Bool
- Servant.Elm.Internal.Generate: isEmptyType :: ElmOptions -> ElmDatatype -> Bool
+ Servant.Elm.Internal.Generate: isEmptyType :: ElmOptions -> EType -> Bool
- Servant.Elm.Internal.Generate: mkArgs :: ElmOptions -> Req ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: mkArgs :: ElmOptions -> Req EType -> Doc
- Servant.Elm.Internal.Generate: mkLetParams :: ElmOptions -> Req ElmDatatype -> Maybe Doc
+ Servant.Elm.Internal.Generate: mkLetParams :: ElmOptions -> Req EType -> Maybe Doc
- Servant.Elm.Internal.Generate: mkQueryParams :: Req ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: mkQueryParams :: Req EType -> Doc
- Servant.Elm.Internal.Generate: mkRequest :: ElmOptions -> Req ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: mkRequest :: ElmOptions -> Req EType -> Doc
- Servant.Elm.Internal.Generate: mkTypeSignature :: ElmOptions -> Req ElmDatatype -> Doc
+ Servant.Elm.Internal.Generate: mkTypeSignature :: ElmOptions -> Req EType -> Doc
- Servant.Elm.Internal.Generate: mkUrl :: ElmOptions -> [Segment ElmDatatype] -> Doc
+ Servant.Elm.Internal.Generate: mkUrl :: ElmOptions -> [Segment EType] -> Doc
Files
- CHANGELOG.md +5/−0
- README.md +73/−48
- examples/books/generate.hs +18/−18
- examples/e2e-tests/generate.hs +25/−38
- examples/giphy/generate.hs +31/−39
- examples/readme-example/generate.hs +18/−17
- servant-elm.cabal +14/−10
- src/Servant/Elm.hs +16/−7
- src/Servant/Elm/Internal/Foreign.hs +14/−8
- src/Servant/Elm/Internal/Generate.hs +237/−106
- src/Servant/Elm/Internal/Options.hs +8/−0
- src/Servant/Elm/Internal/Orphans.hs +0/−21
- test/Common.hs +7/−10
- test/CompileSpec.hs +75/−36
- test/GenerateSpec.hs +56/−14
- test/elm-sources/elm-package.json +0/−16
- test/elm-sources/elm.json +30/−0
- test/elm-sources/getBooksByIdSource.elm +33/−22
- test/elm-sources/getBooksByTitleSource.elm +32/−22
- test/elm-sources/getBooksSource.elm +33/−28
- test/elm-sources/getNothingSource.elm +31/−27
- test/elm-sources/getOneSource.elm +28/−21
- test/elm-sources/getOneWithDynamicUrlSource.elm +28/−21
- test/elm-sources/getWithaheaderSource.elm +32/−25
- test/elm-sources/getWitharesponseheaderSource.elm +28/−21
- test/elm-sources/postBooksSource.elm +34/−27
- test/elm-sources/postTwoSource.elm +28/−21
- test/elm-sources/putNothingSource.elm +31/−27
CHANGELOG.md view
@@ -1,3 +1,8 @@+0.6.0.0+-------++* PR #49. Migrate to elm-bridge and http 2.0.0+ 0.5.0.0 ------- * Fix generation for APIs with response headers.
README.md view
@@ -16,16 +16,15 @@ ```haskell {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -import Elm (Spec (Spec), specsToDir, toElmDecoderSource,- toElmTypeSource)-import GHC.Generics (Generic)+import Elm.Derive (defaultOptions, deriveBoth)+ import Servant.API ((:>), Capture, Get, JSON)-import Servant.Elm (ElmType, Proxy (Proxy), defElmImports,- generateElmForAPI)+import Servant.Elm (DefineElm (DefineElm), Proxy (Proxy), defElmImports, defElmOptions,+ generateElmModuleWith) ``` We have some Haskell-defined types and our Servant API.@@ -33,9 +32,9 @@ ```haskell data Book = Book { name :: String- } deriving (Generic)+ } -instance ElmType Book+deriveBoth defaultOptions ''Book type BooksApi = "books" :> Capture "bookId" Int :> Get '[JSON] Book ```@@ -43,15 +42,18 @@ Now we can generate Elm functions to query the API: ```haskell-spec :: Spec-spec = Spec ["Generated", "MyApi"]- (defElmImports- : toElmTypeSource (Proxy :: Proxy Book)- : toElmDecoderSource (Proxy :: Proxy Book)- : generateElmForAPI (Proxy :: Proxy BooksApi))- main :: IO ()-main = specsToDir [spec] "my-elm-dir"+main =+ generateElmModuleWith+ defElmOptions+ [ "Generated"+ , "MyApi"+ ]+ defElmImports+ "my-elm-dir"+ [ DefineElm (Proxy :: Proxy Book)+ ]+ (Proxy :: Proxy BooksApi) ``` Let's save this as `example.hs` and run it:@@ -65,46 +67,69 @@ Here's what was generated: ```elm-module Generated.MyApi exposing (..)+module Generated.MyApi exposing(..) -import Json.Decode exposing (..)-import Json.Decode.Pipeline exposing (..)-import Json.Encode+import Json.Decode+import Json.Encode exposing (Value)+-- The following module comes from bartavelle/json-helpers+import Json.Helpers exposing (..)+import Dict exposing (Dict)+import Set import Http import String+import Url.Builder +maybeBoolToIntStr : Maybe Bool -> String+maybeBoolToIntStr mx =+ case mx of+ Nothing -> ""+ Just True -> "1"+ Just False -> "0" -type alias Book =- { name : String- }+type alias Book =+ { name: String+ } -decodeBook : Decoder Book-decodeBook =- decode Book- |> required "name" string+jsonDecBook : Json.Decode.Decoder ( Book )+jsonDecBook =+ Json.Decode.succeed (\pname -> {name = pname})+ |> required "name" (Json.Decode.string) -getBooksByBookId : Int -> Http.Request (Book)+jsonEncBook : Book -> Value+jsonEncBook val =+ Json.Encode.object+ [ ("name", Json.Encode.string val.name)+ ]+++getBooksByBookId : Int -> Http.Request Book getBooksByBookId capture_bookId =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ ""- , "books"- , capture_bookId |> toString |> Http.encodeUri- ]- , body =- Http.emptyBody- , expect =- Http.expectJson decodeBook- , timeout =- Nothing- , withCredentials =- False- }+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.absolute+ [ "books"+ , capture_bookId |> String.fromInt+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson <| jsonDecBook+ , timeout =+ Nothing+ , withCredentials =+ False+ } ``` See [`examples`](examples) for a complete usage example, or take a look at
examples/books/generate.hs view
@@ -1,21 +1,19 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -import Elm (Spec (Spec), specsToDir, toElmDecoderSource,- toElmEncoderSource, toElmTypeSource)-import GHC.Generics (Generic) import Servant.API ((:<|>), (:>), Capture, Get, JSON, Post, ReqBody)-import Servant.Elm (ElmOptions (..), ElmType, Proxy (Proxy),- UrlPrefix (Static), defElmImports, defElmOptions,- generateElmForAPIWith)+import Servant.Elm (DefineElm (DefineElm), ElmOptions(urlPrefix),+ Proxy (Proxy), UrlPrefix(Static), defaultOptions,+ defElmImports, defElmOptions, deriveBoth,+ generateElmModuleWith) data Book = Book { name :: String- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq) -instance ElmType Book+deriveBoth defaultOptions ''Book type BooksApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book :<|> "books" :> Get '[JSON] [Book]@@ -24,13 +22,15 @@ myElmOpts :: ElmOptions myElmOpts = defElmOptions { urlPrefix = Static "http://localhost:8000" } -spec :: Spec-spec = Spec ["Generated", "BooksApi"]- (defElmImports- : toElmTypeSource (Proxy :: Proxy Book)- : toElmDecoderSource (Proxy :: Proxy Book)- : toElmEncoderSource (Proxy :: Proxy Book)- : generateElmForAPIWith myElmOpts (Proxy :: Proxy BooksApi))- main :: IO ()-main = specsToDir [spec] "elm"+main =+ generateElmModuleWith+ myElmOpts+ [ "Generated"+ , "BooksApi"+ ]+ defElmImports+ "elm"+ [ DefineElm (Proxy :: Proxy Book)+ ]+ (Proxy :: Proxy BooksApi)
examples/e2e-tests/generate.hs view
@@ -1,18 +1,13 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -import Elm (Spec (Spec), specsToDir, toElmDecoderSource,- toElmEncoderSource, toElmTypeSource)-import GHC.Generics (Generic) import Servant.API ((:<|>), (:>), Capture, Get, GetNoContent, JSON,- NoContent, Post, QueryParam, ReqBody)-import Servant.Elm (ElmOptions (..), ElmType, Proxy (Proxy),- UrlPrefix (Static), defElmImports, defElmOptions,- generateElmForAPIWith)-+ Post, QueryParam, ReqBody)+import Servant.Elm (DefineElm (DefineElm), ElmOptions(..), Proxy (Proxy),+ UrlPrefix (Static), defaultOptions, defElmImports,+ defElmOptions, deriveBoth, generateElmModuleWith) myElmOpts :: ElmOptions myElmOpts = defElmOptions { urlPrefix = Static "https://httpbin.org" }@@ -20,31 +15,30 @@ data MessageBody = MessageBody { message :: String }- deriving (Generic, ElmType) data QueryArgs = QueryArgs { q :: String }- deriving (Generic, ElmType) data Response = Response { origin :: String }- deriving (Generic, ElmType) data ResponseWithJson = ResponseWithJson { json :: MessageBody }- deriving (Generic, ElmType) data ResponseWithArgs = ResponseWithArgs { args :: QueryArgs }- deriving (Generic, ElmType) +concat <$> mapM+ (deriveBoth defaultOptions)+ [''MessageBody, ''QueryArgs, ''Response, ''ResponseWithJson, ''ResponseWithArgs] + type Api = "ip" :> Get '[JSON] Response :<|> "status" :> "204"- :> GetNoContent '[JSON] NoContent+ :> GetNoContent '[JSON] () :<|> "post" :> ReqBody '[JSON] MessageBody :> Post '[JSON] ResponseWithJson@@ -55,26 +49,19 @@ :> Get '[JSON] Response -spec :: Spec-spec =- Spec ["Generated", "Api"]- (defElmImports- : toElmTypeSource (Proxy :: Proxy Response)- : toElmDecoderSource (Proxy :: Proxy Response)- : toElmTypeSource (Proxy :: Proxy NoContent)- : toElmTypeSource (Proxy :: Proxy MessageBody)- : toElmEncoderSource (Proxy :: Proxy MessageBody)- : toElmDecoderSource (Proxy :: Proxy MessageBody)- : toElmTypeSource (Proxy :: Proxy ResponseWithJson)- : toElmDecoderSource (Proxy :: Proxy ResponseWithJson)- : toElmTypeSource (Proxy :: Proxy QueryArgs)- : toElmDecoderSource (Proxy :: Proxy QueryArgs)- : toElmTypeSource (Proxy :: Proxy ResponseWithArgs)- : toElmDecoderSource (Proxy :: Proxy ResponseWithArgs)- : generateElmForAPIWith- myElmOpts- (Proxy :: Proxy Api))-- main :: IO ()-main = specsToDir [spec] "elm"+main =+ generateElmModuleWith+ myElmOpts+ [ "Generated"+ , "Api"+ ]+ defElmImports+ "elm"+ [ DefineElm (Proxy :: Proxy MessageBody)+ , DefineElm (Proxy :: Proxy QueryArgs)+ , DefineElm (Proxy :: Proxy Response)+ , DefineElm (Proxy :: Proxy ResponseWithJson)+ , DefineElm (Proxy :: Proxy ResponseWithArgs)+ ]+ (Proxy :: Proxy Api)
examples/giphy/generate.hs view
@@ -1,62 +1,54 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -import qualified Data.Text as T-import Elm (Options, Spec (Spec), defaultOptions,- fieldLabelModifier, specsToDir,- toElmDecoderSourceWith, toElmTypeSourceWith)-import GHC.Generics (Generic)+import qualified Elm.Derive as Elm+ import Servant.API ((:>), Get, JSON, QueryParam)-import Servant.Elm (ElmOptions (..), ElmType, Proxy (Proxy), UrlPrefix(Static),- defElmImports, defElmOptions,- generateElmForAPIWith)+import Servant.Elm (DefineElm (DefineElm), ElmOptions (urlPrefix),+ Proxy (Proxy), UrlPrefix (Static), defElmImports,+ defElmOptions, defaultOptions, deriveBoth,+ generateElmModuleWith) data GifData = GifData { image_url :: String- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq) data Gif = Gif { _data :: GifData- } deriving (Show, Eq, Generic)--instance ElmType GifData-instance ElmType Gif--stripUnderscore :: T.Text -> T.Text-stripUnderscore field =- if T.head field == '_' then- T.tail field- else- field+ } deriving (Show, Eq) -options :: Elm.Options-options =- Elm.defaultOptions- { Elm.fieldLabelModifier = stripUnderscore }+concat <$> mapM+ (deriveBoth defaultOptions+ { Elm.fieldLabelModifier = \ field ->+ if head field == '_' then+ tail field+ else+ field+ , Elm.unwrapUnaryRecords = False+ }+ ) [''GifData, ''Gif] myElmOpts :: ElmOptions myElmOpts = defElmOptions { urlPrefix = Static "http://api.giphy.com/v1/gifs"- , elmExportOptions =- options } type GiphyApi = "random" :> QueryParam "api_key" String :> QueryParam "tag" String :> Get '[JSON] Gif -giphySpec :: Spec-giphySpec = Spec ["Generated", "GiphyApi"]- (defElmImports- : toElmTypeSourceWith options (Proxy :: Proxy Gif)- : toElmTypeSourceWith options (Proxy :: Proxy GifData)- : toElmDecoderSourceWith options (Proxy :: Proxy Gif)- : toElmDecoderSourceWith options (Proxy :: Proxy GifData)- : generateElmForAPIWith- myElmOpts- (Proxy :: Proxy GiphyApi))- main :: IO ()-main = specsToDir [giphySpec] "elm"+main =+ generateElmModuleWith+ myElmOpts+ [ "Generated"+ , "GiphyApi"+ ]+ defElmImports+ "elm"+ [ DefineElm (Proxy :: Proxy Gif)+ , DefineElm (Proxy :: Proxy GifData)+ ]+ (Proxy :: Proxy GiphyApi)
examples/readme-example/generate.hs view
@@ -1,29 +1,30 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -import Elm (Spec (Spec), specsToDir, toElmDecoderSource,- toElmTypeSource)-import GHC.Generics (Generic) import Servant.API ((:>), Capture, Get, JSON)-import Servant.Elm (ElmType, Proxy (Proxy), defElmImports,- generateElmForAPI)+import Servant.Elm (DefineElm (DefineElm), Proxy (Proxy),+ defaultOptions, defElmImports, defElmOptions,+ deriveBoth, generateElmModuleWith) data Book = Book- { name :: String- } deriving ((Generic))+ { name :: String+ } -instance ElmType Book+deriveBoth defaultOptions ''Book type BooksApi = "books" :> Capture "bookId" Int :> Get '[JSON] Book -spec :: Spec-spec = Spec ["Generated", "MyApi"]- (defElmImports- : toElmTypeSource (Proxy :: Proxy Book)- : toElmDecoderSource (Proxy :: Proxy Book)- : generateElmForAPI (Proxy :: Proxy BooksApi))- main :: IO ()-main = specsToDir [spec] "my-elm-dir"+main =+ generateElmModuleWith+ defElmOptions+ [ "Generated"+ , "MyApi"+ ]+ defElmImports+ "my-elm-dir"+ [ DefineElm (Proxy :: Proxy Book)+ ]+ (Proxy :: Proxy BooksApi)
servant-elm.cabal view
@@ -1,5 +1,5 @@ name: servant-elm-version: 0.5.0.0+version: 0.6.0.0 synopsis: Automatically derive Elm functions to query servant webservices. description: Please see README.md homepage: http://github.com/mattjbray/servant-elm#readme@@ -13,7 +13,7 @@ extra-source-files: CHANGELOG.md README.md- test/elm-sources/elm-package.json+ test/elm-sources/elm.json test/elm-sources/*.elm cabal-version: >=1.10 @@ -30,9 +30,11 @@ exposed-modules: Servant.Elm , Servant.Elm.Internal.Foreign , Servant.Elm.Internal.Generate- , Servant.Elm.Internal.Orphans+ , Servant.Elm.Internal.Options build-depends: base >= 4.7 && < 5- , elm-export >= 0.5+ , aeson >= 0.9+ , directory+ , elm-bridge >= 0.4 , lens , servant >= 0.8 , servant-foreign >= 0.8@@ -51,9 +53,10 @@ , HUnit , aeson >= 0.9 , base- , elm-export >= 0.5+ , elm-bridge >= 0.4 , hspec , servant+ , servant-client , servant-elm , text ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall@@ -69,7 +72,7 @@ build-depends: aeson >= 0.9 , base , directory- , elm-export >= 0.5+ , elm-bridge >= 0.4 , hspec , interpolate , mockery@@ -77,6 +80,7 @@ , servant , servant-elm , text+ , typed-process ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall default-language: Haskell2010 @@ -89,7 +93,7 @@ Buildable: False main-is: generate.hs build-depends: base >= 4.7 && < 5- , elm-export >= 0.5+ , elm-bridge >= 0.4 , servant >= 0.8 , servant-elm hs-source-dirs: examples/books@@ -101,7 +105,7 @@ Buildable: False main-is: generate.hs build-depends: base >= 4.7 && < 5- , elm-export >= 0.5+ , elm-bridge >= 0.4 , servant >= 0.8 , servant-elm hs-source-dirs: examples/e2e-tests@@ -113,7 +117,7 @@ Buildable: False main-is: generate.hs build-depends: base >= 4.7 && < 5- , elm-export >= 0.5+ , elm-bridge >= 0.4 , servant >= 0.8 , servant-elm , text@@ -126,7 +130,7 @@ Buildable: False main-is: generate.hs build-depends: base >= 4.7 && < 5- , elm-export >= 0.5+ , elm-bridge >= 0.4 , servant >= 0.8 , servant-elm hs-source-dirs: examples/readme-example
src/Servant/Elm.hs view
@@ -14,14 +14,20 @@ module Servant.Elm ( generateElmForAPI , generateElmForAPIWith+ , generateElmModule+ , generateElmModuleWith , ElmOptions(..) , UrlPrefix(..) , defElmOptions , defElmImports+ , defaultOptions -- * Convenience re-exports from the "Elm" module- , Spec(Spec)- , ElmType- , specsToDir+ , DefineElm (..)+ , EType (..)+ , defaultTypeAlterations+ , toElmType+ , deriveBoth+ , deriveElmDef -- * Convenience re-exports from "Data.Proxy" , Proxy(Proxy) ) where@@ -29,8 +35,11 @@ import Servant.Elm.Internal.Generate (ElmOptions (..), UrlPrefix (..), defElmImports, defElmOptions, generateElmForAPI,- generateElmForAPIWith)-+ generateElmForAPIWith,+ generateElmModule,+ generateElmModuleWith)+import Servant.Elm.Internal.Options (defaultOptions) import Data.Proxy (Proxy (Proxy))-import Elm (ElmType, Spec (Spec),- specsToDir)+import Elm.TyRep (EType (..), toElmType)+import Elm.Module (DefineElm (..), defaultTypeAlterations)+import Elm.Derive (deriveBoth, deriveElmDef)
src/Servant/Elm/Internal/Foreign.hs view
@@ -7,21 +7,27 @@ module Servant.Elm.Internal.Foreign where import Data.Proxy (Proxy (Proxy))-import Elm (ElmDatatype, ElmType, toElmType)+import Data.Typeable (Typeable)+import Elm.TyRep (EType, toElmType)+import Servant.API (Headers(..)) import Servant.Foreign (Foreign, GenerateList, HasForeign, HasForeignType, Req, listFromAPI, typeFor) data LangElm -instance (ElmType a) => HasForeignType LangElm ElmDatatype a where- typeFor _ _ _ =- toElmType (Proxy :: Proxy a)+--- TODO: Generate Elm functions that can handle the response headers. PRs+--- welcome!+instance {-# OVERLAPPING #-} (Typeable a) => HasForeignType LangElm EType (Headers b a) where+ typeFor _ _ _ = toElmType (Proxy :: Proxy a) +instance {-# OVERLAPPABLE #-} (Typeable a) => HasForeignType LangElm EType a where+ typeFor _ _ _ = toElmType (Proxy :: Proxy a)+ getEndpoints- :: ( HasForeign LangElm ElmDatatype api- , GenerateList ElmDatatype (Foreign ElmDatatype api))+ :: ( HasForeign LangElm EType api+ , GenerateList EType (Foreign EType api)) => Proxy api- -> [Req ElmDatatype]+ -> [Req EType] getEndpoints =- listFromAPI (Proxy :: Proxy LangElm) (Proxy :: Proxy ElmDatatype)+ listFromAPI (Proxy :: Proxy LangElm) (Proxy :: Proxy EType)
src/Servant/Elm/Internal/Generate.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}@@ -5,22 +6,36 @@ import Prelude hiding ((<$>)) import Control.Lens (to, (^.))-import Data.List (nub)+import Data.List (intercalate, intersperse, nub) import Data.Maybe (catMaybes)-import Data.Proxy (Proxy)+import Data.Proxy (Proxy(..)) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as L import qualified Data.Text.Encoding as T-import Elm (ElmDatatype(..), ElmPrimitive(..))-import qualified Elm-import Servant.API (NoContent (..))+import Data.Text.IO as TIO++import Elm.Json (jsonParserForType, jsonSerForType)+import qualified Elm.Module as Elm+import Elm.TyRep (ETCon(..), EType(..), ETypeDef(..), toElmType)+import Elm.TyRender (renderElm)+import Elm.Versions (ElmVersion(Elm0p18))+ import Servant.Elm.Internal.Foreign (LangElm, getEndpoints)-import Servant.Elm.Internal.Orphans () import qualified Servant.Foreign as F+import System.Directory (createDirectoryIfMissing) import Text.PrettyPrint.Leijen.Text +toElmTypeRefWith :: ElmOptions -> EType -> Text+toElmTypeRefWith ElmOptions{..} = T.pack . renderElm . elmTypeAlterations++toElmDecoderRefWith :: ElmOptions -> EType -> Text+toElmDecoderRefWith ElmOptions{..} = T.pack . jsonParserForType . elmTypeAlterations++toElmEncoderRefWith :: ElmOptions -> EType -> Text+toElmEncoderRefWith ElmOptions{..} = T.pack . jsonSerForType . elmTypeAlterations+ {-| Options to configure how code is generated. -}@@ -34,11 +49,13 @@ argument. -} urlPrefix :: UrlPrefix- , elmExportOptions :: Elm.Options- -- ^ Options to pass to elm-export- , emptyResponseElmTypes :: [ElmDatatype]+ , elmTypeAlterations :: (EType -> EType)+ -- ^ Alterations to perform on ETypes before code generation.+ , elmAlterations :: (ETypeDef -> ETypeDef)+ -- ^ Alterations to perform on ETypeDefs before code generation.+ , emptyResponseElmTypes :: [EType] -- ^ Types that represent an empty Http response.- , stringElmTypes :: [ElmDatatype]+ , stringElmTypes :: [EType] -- ^ Types that represent a String. } @@ -47,6 +64,7 @@ = Static T.Text | Dynamic +type Namespace = [String] {-| Default options for generating Elm code.@@ -55,25 +73,26 @@ > { urlPrefix = > Static ""-> , elmExportOptions =-> Elm.defaultOptions+> , elmAlterations =+> Elm.defaultTypeAlterations > , emptyResponseElmTypes =-> [ toElmType NoContent ]+> [ getType (Proxy :: Proxy ()) ] > , stringElmTypes =-> [ toElmType "" ]+> [ getType (Proxy :: Proxy String)+> , getType (Proxy :: Proxy T.Text) ] > } -} defElmOptions :: ElmOptions defElmOptions = ElmOptions { urlPrefix = Static ""- , elmExportOptions = Elm.defaultOptions+ , elmTypeAlterations = Elm.defaultTypeAlterations+ , elmAlterations = Elm.defaultAlterations , emptyResponseElmTypes =- [ Elm.toElmType NoContent- , Elm.toElmType ()+ [ toElmType (Proxy :: Proxy ()) ] , stringElmTypes =- [ Elm.toElmType ("" :: String)- , Elm.toElmType ("" :: T.Text)+ [ toElmType (Proxy :: Proxy String)+ , toElmType (Proxy :: Proxy T.Text) ] } @@ -85,24 +104,84 @@ The default required imports are: -> import Json.Decode exposing (..)-> import Json.Decode.Pipeline exposing (..)-> import Json.Encode+> import Json.Decode+> import Json.Encode exposing (Value)+> -- The following module comes from bartavelle/json-helpers+> import Json.Helpers exposing (..)+> import Dict exposing (Dict)+> import Set > import Http > import String+> import Url.Builder -} defElmImports :: Text defElmImports = T.unlines- [ "import Json.Decode exposing (..)"- , "import Json.Decode.Pipeline exposing (..)"- , "import Json.Encode"+ [ "import Json.Decode"+ , "import Json.Encode exposing (Value)"+ , "-- The following module comes from bartavelle/json-helpers"+ , "import Json.Helpers exposing (..)"+ , "import Dict exposing (Dict)"+ , "import Set" , "import Http" , "import String"+ , "import Url.Builder"+ , ""+ , "maybeBoolToIntStr : Maybe Bool -> String"+ , "maybeBoolToIntStr mx ="+ , " case mx of"+ , " Nothing -> \"\""+ , " Just True -> \"1\""+ , " Just False -> \"0\"" ] +{-|+Helper to generate a complete Elm module given a list of Elm type definitions+and an API.+-}+generateElmModuleWith ::+ ( F.HasForeign LangElm EType api+ , F.GenerateList EType (F.Foreign EType api)+ )+ => ElmOptions+ -> Namespace+ -> Text+ -> FilePath+ -> [Elm.DefineElm]+ -> Proxy api+ -> IO ()+generateElmModuleWith options namespace imports rootDir typeDefs api = do+ let out =+ T.unlines $+ [ T.pack $ Elm.moduleHeader Elm0p18 moduleName+ , ""+ , imports+ , T.pack $ Elm.makeModuleContentWithAlterations (elmAlterations options) typeDefs+ ] +++ generateElmForAPIWith options api+ moduleName = intercalate "." namespace+ filePath = intercalate "/" $ rootDir:init namespace+ fileName = intercalate "/" $ filePath:[last namespace ++ ".elm"]+ createDirectoryIfMissing True filePath+ TIO.writeFile fileName out {-|+Calls generateElmModuleWith with @defElmOptions@.+-}+generateElmModule ::+ ( F.HasForeign LangElm EType api+ , F.GenerateList EType (F.Foreign EType api)+ )+ => Namespace+ -> Text+ -> FilePath+ -> [Elm.DefineElm]+ -> Proxy api+ -> IO ()+generateElmModule namespace imports filePath typeDefs api =+ generateElmModuleWith defElmOptions namespace imports filePath typeDefs api++{-| Generate Elm code for the API with default options. Returns a list of Elm functions to query your Servant API from Elm.@@ -112,8 +191,8 @@ which handles the module name for you. -} generateElmForAPI- :: ( F.HasForeign LangElm ElmDatatype api- , F.GenerateList ElmDatatype (F.Foreign ElmDatatype api))+ :: ( F.HasForeign LangElm EType api+ , F.GenerateList EType (F.Foreign EType api)) => Proxy api -> [Text] generateElmForAPI =@@ -124,12 +203,12 @@ Generate Elm code for the API with custom options. -} generateElmForAPIWith- :: ( F.HasForeign LangElm ElmDatatype api- , F.GenerateList ElmDatatype (F.Foreign ElmDatatype api))+ :: ( F.HasForeign LangElm EType api+ , F.GenerateList EType (F.Foreign EType api)) => ElmOptions -> Proxy api -> [Text]-generateElmForAPIWith opts =+generateElmForAPIWith opts = intersperse "" . nub . map docToText . map (generateElmForRequest opts) . getEndpoints i :: Int@@ -138,7 +217,7 @@ {-| Generate an Elm function for one endpoint. -}-generateElmForRequest :: ElmOptions -> F.Req ElmDatatype -> Doc+generateElmForRequest :: ElmOptions -> F.Req EType -> Doc generateElmForRequest opts request = funcDef where@@ -159,8 +238,10 @@ ] fnName =- request ^. F.reqFuncName . to (T.replace "-" "" . F.camelCase) . to stext+ request ^. F.reqFuncName . to (replace . F.camelCase) . to stext + replace = T.replace "-" "" . T.replace "." ""+ typeSignature = mkTypeSignature opts request @@ -174,14 +255,14 @@ mkRequest opts request -mkTypeSignature :: ElmOptions -> F.Req ElmDatatype -> Doc+mkTypeSignature :: ElmOptions -> F.Req EType -> Doc mkTypeSignature opts request = (hsep . punctuate " ->" . concat) [ catMaybes [urlPrefixType] , headerTypes , urlCaptureTypes , queryTypes- , catMaybes [bodyType, returnType]+ , catMaybes [bodyType, toMsgType, returnType] ] where urlPrefixType :: Maybe Doc@@ -190,9 +271,9 @@ Dynamic -> Just "String" Static _ -> Nothing - elmTypeRef :: ElmDatatype -> Doc+ elmTypeRef :: EType -> Doc elmTypeRef eType =- stext (Elm.toElmTypeRefWith (elmExportOptions opts) eType)+ stext (toElmTypeRefWith opts eType) headerTypes :: [Doc] headerTypes =@@ -218,25 +299,31 @@ bodyType = fmap elmTypeRef $ request ^. F.reqBody + toMsgType :: Maybe Doc+ toMsgType = do+ result <- fmap elmTypeRef $ request ^. F.reqReturnType+ Just ("(Result Http.Error " <+> parens result <+> " -> msg)")+ returnType :: Maybe Doc returnType = do- result <- fmap elmTypeRef $ request ^. F.reqReturnType- pure ("Http.Request" <+> parens result)+ pure ("Cmd msg") -elmHeaderArg :: F.HeaderArg ElmDatatype -> Doc+elmHeaderArg :: F.HeaderArg EType -> Doc elmHeaderArg header = "header_" <> header ^. F.headerArg . F.argName . to (stext . T.replace "-" "_" . F.unPathSegment) -elmCaptureArg :: F.Segment ElmDatatype -> Doc+elmCaptureArg :: F.Segment EType -> Doc elmCaptureArg segment = "capture_" <>- F.captureArg segment ^. F.argName . to (stext . F.unPathSegment)+ F.captureArg segment ^. F.argName . to (stext . replace . F.unPathSegment)+ where+ replace = T.replace "-" "_" -elmQueryArg :: F.QueryArg ElmDatatype -> Doc+elmQueryArg :: F.QueryArg EType -> Doc elmQueryArg arg = "query_" <> arg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment)@@ -257,7 +344,7 @@ mkArgs :: ElmOptions- -> F.Req ElmDatatype+ -> F.Req EType -> Doc mkArgs opts request = (hsep . concat) $@@ -281,22 +368,21 @@ ] , -- Request body maybe [] (const [elmBodyArg]) (request ^. F.reqBody)+ , pure "toMsg" ] -mkLetParams :: ElmOptions -> F.Req ElmDatatype -> Maybe Doc+mkLetParams :: ElmOptions -> F.Req EType -> Maybe Doc mkLetParams opts request =- if null (request ^. F.reqUrl . F.queryStr) then- Nothing- else Just $ "params =" <$>- indent i ("List.filter (not << String.isEmpty)" <$>- indent i (elmList params))+ indent i ("List.filterMap identity" <$>+ parens ("List.concat" <$>+ indent i (elmList params))) where params :: [Doc] params = map paramToDoc (request ^. F.reqUrl . F.queryStr) - paramToDoc :: F.QueryArg ElmDatatype -> Doc+ paramToDoc :: F.QueryArg EType -> Doc paramToDoc qarg = -- something wrong with indentation here... case qarg ^. F.queryArgType of@@ -309,28 +395,44 @@ if isElmStringType opts argType || isElmMaybeStringType opts argType then "" else- "toString >> "+ "String.fromInt >> " in- (if wrapped then name else "Just" <+> name) <$>- indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Http.encodeUri >> (++)" <+> dquotes (elmName <> equals)) <$>- "|> Maybe.withDefault" <+> dquotes empty)+ "[" <+> (if wrapped then elmName else "Just" <+> elmName) <> line <>+ (indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Url.Builder.string" <+> dquotes elmName)))+ <+> "]"+ -- (if wrapped then name else "Just" <+> name) <$>+ -- indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Http.encodeUri >> (++)" <+> dquotes (elmName <> equals)) <$>+ -- "|> Maybe.withDefault" <+> dquotes empty) F.Flag ->- "if" <+> name <+> "then" <$>- indent 4 (dquotes (name <> equals)) <$>+ "[" <+>+ ("if" <+> elmName <+> "then" <$>+ indent 4 ("Just" <+> parens ("Url.Builder.string" <+> dquotes name <+> dquotes empty)) <$> indent 2 "else" <$>- indent 4 (dquotes empty)+ indent 4 "Nothing")+ <+> "]" F.List ->- name <$>- indent 4 ("|> List.map" <+> parens (backslash <> "val ->" <+> dquotes (name <> "[]=") <+> "++ (val |> toString |> Http.encodeUri)") <$>- "|> String.join" <+> dquotes "&")+ let+ argType = qarg ^. F.queryArgName . F.argType+ convertedVal =+ if isElmListOfMaybeBoolType argType then+ parens ("maybeBoolToIntStr" <+> "val")+ else+ "val"+ in+ elmName <$>+ indent 4 ("|> List.map"+ <+> parens (backslash <> "val ->" <+> "Just"+ <+> parens ("Url.Builder.string"+ <+> dquotes (name <> "[]")+ <+> convertedVal))) where- name = elmQueryArg qarg- elmName= qarg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment)+ elmName = elmQueryArg qarg+ name = qarg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment) -mkRequest :: ElmOptions -> F.Req ElmDatatype -> Doc+mkRequest :: ElmOptions -> F.Req EType -> Doc mkRequest opts request = "Http.request" <$> indent i@@ -348,8 +450,8 @@ indent i expect , "timeout =" <$> indent i "Nothing"- , "withCredentials =" <$>- indent i "False"+ , "tracker =" <$>+ indent i "Nothing" ]) where method =@@ -364,7 +466,7 @@ if isElmMaybeStringType opts argType || isElmStringType opts argType then mempty else- " << toString"+ " << String.fromInt" in "Maybe.map" <+> parens (("Http.header" <+> dquotes headerName <> toStringSrc)) <+>@@ -388,42 +490,69 @@ Just elmTypeExpr -> let encoderName =- Elm.toElmEncoderRefWith (elmExportOptions opts) elmTypeExpr+ toElmEncoderRefWith opts elmTypeExpr in "Http.jsonBody" <+> parens (stext encoderName <+> elmBodyArg) expect = case request ^. F.reqReturnType of- Just elmTypeExpr | isEmptyType opts elmTypeExpr ->- let elmConstructor =- Elm.toElmTypeRefWith (elmExportOptions opts) elmTypeExpr- in- "Http.expectStringResponse" <$>- indent i (parens (backslash <> braces " body " <+> "->" <$>- indent i ("if String.isEmpty body then" <$>- indent i "Ok" <+> stext elmConstructor <$>- "else" <$>- indent i ("Err" <+> dquotes "Expected the response body to be empty")) <> line))+ Just elmTypeExpr+ | isEmptyType opts elmTypeExpr+ -- let elmConstructor = T.pack (renderElm elmTypeExpr)+ ->+ "Http.expectString " <> line <+> indent i "(\\x -> case x of" <> line <+>+ indent i "Err e -> toMsg (Err e)" <> line <+>+ indent i "Ok _ -> toMsg (Ok ()))"+ Just elmTypeExpr ->+ "Http.expectJson toMsg" <+> renderDecoderName elmTypeExpr+ Nothing -> error "mkHttpRequest: no reqReturnType?"+ -- case request ^. F.reqReturnType of+ -- Just elmTypeExpr | isEmptyType opts elmTypeExpr ->+ -- let elmConstructor =+ -- toElmTypeRefWith opts elmTypeExpr+ -- in+ -- "Http.expectStringResponse" <$>+ -- indent i (parens (backslash <> " rsp " <+> "->" <$>+ -- indent i ("if String.isEmpty rsp.body then" <$>+ -- indent i "Ok" <+> stext elmConstructor <$>+ -- "else" <$>+ -- indent i ("Err" <+> dquotes "Expected the response body to be empty")) <> line)) - Just elmTypeExpr ->- "Http.expectJson" <+> stext (Elm.toElmDecoderRefWith (elmExportOptions opts) elmTypeExpr)+ -- Just elmTypeExpr ->+ -- "Http.expectJson <|" <+> stext (toElmDecoderRefWith opts elmTypeExpr) - Nothing ->- error "mkHttpRequest: no reqReturnType?"+ -- Nothing ->+ -- error "mkHttpRequest: no reqReturnType?" +renderDecoderName :: EType -> Doc+renderDecoderName elmTypeExpr =+ case elmTypeExpr of+ ETyApp (ETyCon (ETCon "List")) t ->+ parens ("Json.Decode.list " <> parens (renderDecoderName t))+ ETyApp (ETyCon (ETCon "Maybe")) t ->+ parens ("Json.Decode.maybe " <> parens (renderDecoderName t))+ ETyCon (ETCon "Int") -> "Json.Decode.int"+ ETyCon (ETCon "String") -> "Json.Decode.string"+ _ -> ("jsonDec" <> stext (T.pack (renderElm elmTypeExpr))) -mkUrl :: ElmOptions -> [F.Segment ElmDatatype] -> Doc++mkUrl :: ElmOptions -> [F.Segment EType] -> Doc mkUrl opts segments =- "String.join" <+> dquotes "/" <$>- (indent i . elmList)- ( case urlPrefix opts of- Dynamic -> "urlBase"- Static url -> dquotes (stext url)- : map segmentToDoc segments)+ urlBuilder <$>+ (indent i . elmList)+ ( map segmentToDoc segments)+ -- ( case urlPrefix opts of+ -- Dynamic -> "urlBase"+ -- Static url -> dquotes (stext url)+ -- : map segmentToDoc segments) where+ urlBuilder :: Doc+ urlBuilder = case urlPrefix opts of+ Dynamic -> "Url.Builder.absolute" :: Doc+ Static url -> "Url.Builder.crossOrigin" <+> dquotes (stext url) - segmentToDoc :: F.Segment ElmDatatype -> Doc+ segmentToDoc :: F.Segment EType -> Doc segmentToDoc s = case F.unSegment s of F.Static path ->@@ -435,28 +564,25 @@ if isElmStringType opts (arg ^. F.argType) then empty else- " |> toString"+ " |> String.fromInt" in- (elmCaptureArg s) <> toStringSrc <> " |> Http.encodeUri"+ (elmCaptureArg s) <> toStringSrc mkQueryParams- :: F.Req ElmDatatype+ :: F.Req EType -> Doc-mkQueryParams request =- if null (request ^. F.reqUrl . F.queryStr) then- empty- else- line <> "++" <+> align ("if List.isEmpty params then" <$>- indent i (dquotes empty) <$>- "else" <$>- indent i (dquotes "?" <+> "++ String.join" <+> dquotes "&" <+> "params"))+mkQueryParams _request =+ -- if null (request ^. F.reqUrl . F.queryStr) then+ -- empty+ -- else+ line <> indent 4 (align "params") {- | Determines whether we construct an Elm function that expects an empty response body. -}-isEmptyType :: ElmOptions -> ElmDatatype -> Bool+isEmptyType :: ElmOptions -> EType -> Bool isEmptyType opts elmTypeExpr = elmTypeExpr `elem` emptyResponseElmTypes opts @@ -464,20 +590,25 @@ {- | Determines whether we call `toString` on URL captures and query params of this type in Elm. -}-isElmStringType :: ElmOptions -> ElmDatatype -> Bool+isElmStringType :: ElmOptions -> EType -> Bool 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 :: ElmOptions -> EType -> Bool+isElmMaybeStringType opts (ETyApp (ETyCon (ETCon "Maybe")) elmTypeExpr) = elmTypeExpr `elem` stringElmTypes opts isElmMaybeStringType _ _ = False -isElmMaybeType :: ElmDatatype -> Bool-isElmMaybeType (ElmPrimitive (EMaybe _)) = True+isElmMaybeType :: EType -> Bool+isElmMaybeType (ETyApp (ETyCon (ETCon "Maybe")) _) = True isElmMaybeType _ = False +isElmListOfMaybeBoolType :: EType -> Bool+isElmListOfMaybeBoolType t =+ case t of+ (ETyApp (ETyCon (ETCon "List")) (ETyApp (ETyCon (ETCon "Maybe")) (ETyCon (ETCon "Bool")))) -> True+ _ -> False -- Doc helpers
+ src/Servant/Elm/Internal/Options.hs view
@@ -0,0 +1,8 @@+module Servant.Elm.Internal.Options where++import qualified Data.Aeson as A+import qualified Elm.Derive as E++defaultOptions :: A.Options+defaultOptions = E.defaultOptions { A.unwrapUnaryRecords = False}+
− src/Servant/Elm/Internal/Orphans.hs
@@ -1,21 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Servant.Elm.Internal.Orphans where--import Elm (ElmDatatype, ElmType, toElmType)-import Servant.API (NoContent, Headers, getResponse)---instance ElmType ElmDatatype where- toElmType = id---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
@@ -1,24 +1,21 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} module Common where -import Data.Aeson (ToJSON) import Data.Proxy (Proxy (Proxy)) import Data.Text (Text)-import Elm (ElmType)-import GHC.Generics (Generic) import Servant.API ((:<|>), (:>), Capture, Get, GetNoContent, Header,- Header', Headers, JSON, NoContent, Post,+ Header', Headers, JSON, Post, PostNoContent, Put, QueryFlag, QueryParam, QueryParam', QueryParams, ReqBody, Required)+import Servant.Elm (deriveBoth, defaultOptions) data Book = Book { title :: String- } deriving (Generic)+ } -instance ToJSON Book-instance ElmType Book+deriveBoth defaultOptions ''Book type TestApi = "one"@@ -41,9 +38,9 @@ :> Get '[JSON] [Book] :<|> "books" :> ReqBody '[JSON] Book- :> PostNoContent '[JSON] NoContent+ :> PostNoContent '[JSON] () :<|> "nothing"- :> GetNoContent '[JSON] NoContent+ :> GetNoContent '[JSON] () :<|> "nothing" :> Put '[JSON] () -- old way to specify no content :<|> "with-a-header"
test/CompileSpec.hs view
@@ -8,23 +8,23 @@ import Control.Exception import Control.Monad (when)+import Data.List (intercalate) import Data.String.Interpolate import Data.String.Interpolate.Util-import qualified Data.Text as T-import qualified Data.Text.IO as T-import Elm (toElmDecoderSource,- toElmEncoderSource,- toElmTypeSource)-import Servant.API (NoContent)+import Elm.Versions (ElmVersion(..)) import Servant.Elm import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, getCurrentDirectory, removeFile, setCurrentDirectory)-import System.Process+import System.Process (callCommand)+import System.Process.Typed+import qualified Data.Text as T+import qualified Elm.Module as Elm+import Data.Text.IO as TIO -import Common (Book, testApi)+import Common (Book, TestApi) main :: IO () main =@@ -35,23 +35,50 @@ describe "generateElmForAPI" $ do it "creates compilable javascript" $ do inTempElmDir $ do- let generated =- T.intercalate "\n\n" $- defElmImports :- [ toElmTypeSource (Proxy :: Proxy NoContent)- , toElmTypeSource (Proxy :: Proxy Book)- , toElmDecoderSource (Proxy :: Proxy Book)- , toElmEncoderSource (Proxy :: Proxy Book)- ] ++- generateElmForAPI testApi- T.writeFile "Api.elm" generated- callCommand "elm-make Api.elm --output api.js"+ let options = defElmOptions+ namespace :: [String]+ namespace = ["Generated", "BooksApi"]+ imports = defElmImports+ rootDir = "."+ typeDefs = [ DefineElm (Proxy :: Proxy Book)+ ]+ api = (Proxy :: Proxy TestApi)+ let out =+ T.unlines $+ [ T.pack $ Elm.moduleHeader Elm0p18 moduleName+ , ""+ , imports+ , ""+ , "type NoContent = NoContent"+ , ""+ , T.pack $ Elm.makeModuleContentWithAlterations (elmAlterations options) typeDefs+ ] +++ generateElmForAPIWith options api+ moduleName = T.unpack (T.intercalate "." (map T.pack namespace))+ filePath = intercalate "/" $ rootDir:init namespace+ fileName = intercalate "/" $ filePath : [last namespace ++ ".elm"]+ createDirectoryIfMissing True filePath+ TIO.writeFile fileName out + -- generateElmModuleWith+ -- defElmOptions+ -- ["Generated", "BooksApi"]+ -- defElmImports+ -- "."+ -- [ DefineElm (Proxy :: Proxy Book)+ -- , DefineElm (Proxy :: Proxy NoContent)+ -- ]+ -- (Proxy :: Proxy TestApi)+ -- -- Useful for locally checking out sources in your tmp dir+ callCommand "cp -r . /home/kb/tmp/servelmtest"+ runProcess_ "elm make Generated/BooksApi.elm --output api.js"++ inTempElmDir :: IO a -> IO a inTempElmDir action = do+ callCommand "rm -rf ./_test-cache" cacheExists <- doesDirectoryExist "_test-cache"- when (not cacheExists)- createCache+ when (not cacheExists) createCache cacheDir <- canonicalizePath "_test-cache" inTempDirectory $ do callCommand ("cp -r " ++ cacheDir ++ "/* .")@@ -61,32 +88,44 @@ createCache = do createDirectoryIfMissing True "_test-cache" withCurrentDirectory "_test-cache" $ do- writeFile "elm-package.json" $ unindent $ [i|+ TIO.writeFile "elm.json" $ T.pack $ unindent $ [i| {- "version": "1.0.0",- "summary": "helpful summary of your project, less than 80 characters",- "repository": "https://github.com/user/project.git",- "license": "BSD3",+ "type": "application", "source-directories": [ "." ],- "exposed-modules": [],+ "elm-version": "0.19.0",+ "version": "1.0.0",+ "summary": "helpful summary of your project, less than 80 characters", "dependencies": {- "elm-lang/core": "5.0.0 <= v < 6.0.0",- "elm-community/json-extra": "2.0.0 <= v < 3.0.0",- "elm-lang/http": "1.0.0 <= v < 2.0.0",- "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0"+ "direct": {+ "elm/core": "1.0.2",+ "elm/json": "1.1.3",+ "elm/http": "2.0.0",+ "elm/url": "1.0.0",+ "bartavelle/json-helpers": "2.0.2"+ },+ "indirect": {+ "elm/bytes": "1.0.8",+ "elm/file": "1.0.5",+ "elm/time": "1.0.0"+ } },- "elm-version": "0.18.0 <= v < 0.19.0"+ "test-dependencies": {+ "direct": {},+ "indirect": {}+ } } |]- callCommand "elm-package install --yes"- compileDependencies+ -- callCommand "elm install"+ -- compileDependencies compileDependencies :: IO () compileDependencies = do- writeFile "Main.elm" "foo = 42"- callCommand "elm-make Main.elm --output main.js"+ TIO.writeFile "Main.elm" "module Main exposing (foo)\n\nfoo : Int\nfoo = 42\n"+ callCommand "echo '>>>>>> Main.elm'"+ callCommand "cat Main.elm"+ callCommand "elm make Main.elm --output main.js" removeFile "Main.elm" removeFile "main.js"
test/GenerateSpec.hs view
@@ -15,7 +15,7 @@ import Servant.API ((:>), Get, JSON) import Servant.Elm import Test.Hspec (Spec, describe, hspec, it)-import Test.HUnit (Assertion, assertBool)+import Test.HUnit (Assertion, assertEqual) import Common (testApi) @@ -35,40 +35,78 @@ [ ( "test/elm-sources/getOneSource.elm" , "module GetOneSource exposing (..)\n\n" <> "import Http\n" <>- "import Json.Decode exposing (..)\n\n\n")+ "import Json.Decode exposing (..)\n" <>+ "import Url.Builder\n\n\n"+ ) , ( "test/elm-sources/postTwoSource.elm" , "module PostTwoSource exposing (..)\n\n" <> "import Http\n" <> "import Json.Decode exposing (..)\n" <>- "import Json.Encode\n\n\n")+ "import Json.Encode\n" <>+ "import Url.Builder\n\n\n"+ ) , ( "test/elm-sources/getBooksByIdSource.elm" , "module GetBooksByIdSource exposing (..)\n\n" <>- "import Http\n\n\n")+ "import Http\n" <>+ "import Url.Builder\n" <>+ "import Json.Decode\n" <>+ "\n" <>+ "type Book = Book\n" <>+ "jsonDecBook : Json.Decode.Decoder Book\n" <>+ "jsonDecBook = Debug.todo \"\"\n\n"+ ) , ( "test/elm-sources/getBooksByTitleSource.elm" , "module GetBooksByTitleSource exposing (..)\n\n" <>- "import Http\n\n\n")+ "import Http\n" <>+ "import Url.Builder\n" <>+ "import Json.Decode as J\n\n" <>+ "type alias Book = {}\n" <>+ "jsonDecBook = J.succeed {}\n\n"+ ) , ( "test/elm-sources/getBooksSource.elm" , "module GetBooksSource exposing (..)\n\n" <> "import Http\n" <>- "import Json.Decode exposing (..)\n\n\n")+ "import Json.Decode exposing (..)\n" <>+ "import Url.Builder\n" <>+ "import Json.Decode as J\n\n" <>+ "type alias Book = {}\n\n" <>+ "maybeBoolToIntStr : Maybe Bool -> String\n" <>+ "maybeBoolToIntStr mx =\n" <>+ " case mx of\n" <>+ " Nothing -> \"\"\n" <>+ " Just True -> \"1\"\n" <>+ " Just False -> \"0\"\n\n" <>+ "jsonDecBook = J.succeed {}\n\n"+ ) , ( "test/elm-sources/postBooksSource.elm" , "module PostBooksSource exposing (..)\n\n" <>- "import Http\n\n\n")+ "import Http\n" <>+ "import Url.Builder\n" <>+ "import Json.Encode as Enc\n\n" <>+ "type alias Book = {}\n" <>+ "jsonEncBook = \\b -> Enc.object []\n\n"+ ) , ( "test/elm-sources/getNothingSource.elm" , "module GetNothingSource exposing (..)\n\n" <>- "import Http\n\n\n")+ "import Http\n" <>+ "import Url.Builder\n\n\n"+ ) , ( "test/elm-sources/putNothingSource.elm" , "module PutNothingSource exposing (..)\n\n" <>- "import Http\n\n\n")+ "import Http\n" <>+ "import Url.Builder\n\n\n"+ ) , ( "test/elm-sources/getWithaheaderSource.elm" , "module GetWithAHeaderSource exposing (..)\n\n" <> "import Http\n" <>+ "import Url.Builder\n" <> "import Json.Decode exposing (..)\n\n\n") , ( "test/elm-sources/getWitharesponseheaderSource.elm" , "module GetWithAResponseHeaderSource exposing (..)\n\n" <> "import Http\n" <>+ "import Url.Builder\n" <> "import Json.Decode exposing (..)\n\n\n")]- let generated = map (<> "\n") (generateElmForAPI testApi)+ let generated = filter (not . T.null) (generateElmForAPI testApi) generated `itemsShouldBe` expected it "with dynamic URLs" $ do expected <-@@ -79,6 +117,7 @@ [ ( "test/elm-sources/getOneWithDynamicUrlSource.elm" , "module GetOneWithDynamicUrlSource exposing (..)\n\n" <> "import Http\n" <>+ "import Url.Builder\n" <> "import Json.Decode exposing (..)\n\n\n")] let generated = map@@ -99,10 +138,13 @@ shouldBeDiff :: Text -> (String, Text, Text) -> Assertion shouldBeDiff a (fpath,header,b) =- assertBool+ assertEqual ("< generated\n" <> "> " <> fpath <> "\n" <> Diff.ppDiff (Diff.getGroupedDiff- (lines (T.unpack (header <> a)))- (lines (T.unpack b))))- (header <> a == b)+ (lines (T.unpack actual))+ (lines (T.unpack expected))))+ actual expected+ where+ actual = T.strip $ header <> a+ expected = T.strip b
− test/elm-sources/elm-package.json
@@ -1,16 +0,0 @@-{- "version": "1.0.0",- "summary": "helpful summary of your project, less than 80 characters",- "repository": "https://github.com/user/project.git",- "license": "BSD3",- "source-directories": [- "."- ],- "exposed-modules": [],- "dependencies": {- "elm-lang/core": "5.0.0 <= v < 6.0.0",- "elm-lang/html": "2.0.0 <= v < 3.0.0",- "elm-lang/http": "1.0.0 <= v < 2.0.0"- },- "elm-version": "0.18.0 <= v < 0.19.0"-}
+ test/elm-sources/elm.json view
@@ -0,0 +1,30 @@+{+ "type": "application",+ "version": "1.0.0",+ "summary": "helpful summary of your project, less than 80 characters",+ "repository": "https://github.com/user/project.git",+ "license": "BSD3",+ "source-directories": [+ "."+ ],+ "exposed-modules": [],+ "dependencies": {+ "direct": {+ "elm/core": "1.0.2",+ "elm/json": "1.1.3",+ "elm/http": "2.0.0",+ "elm/url": "1.0.0",+ "bartavelle/json-helpers": "2.0.2"+ },+ "indirect": {+ "elm/bytes": "1.0.8",+ "elm/file": "1.0.5",+ "elm/time": "1.0.0"+ }+ },+ "test-dependencies": {+ "direct": {},+ "indirect": {}+ },+ "elm-version": "0.19.0"+}
test/elm-sources/getBooksByIdSource.elm view
@@ -1,27 +1,38 @@ module GetBooksByIdSource exposing (..) import Http+import Url.Builder+import Json.Decode +type Book = Book+jsonDecBook : Json.Decode.Decoder Book+jsonDecBook = Debug.todo "" -getBooksById : Int -> Http.Request (Book)-getBooksById capture_id =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ ""- , "books"- , capture_id |> toString |> Http.encodeUri- ]- , body =- Http.emptyBody- , expect =- Http.expectJson decodeBook- , timeout =- Nothing- , withCredentials =- False- }+getBooksById : Int -> (Result Http.Error (Book) -> msg) -> Cmd msg+getBooksById capture_id toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "books"+ , capture_id |> String.fromInt+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg jsonDecBook+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getBooksByTitleSource.elm view
@@ -1,27 +1,37 @@ module GetBooksByTitleSource exposing (..) import Http+import Url.Builder+import Json.Decode as J +type alias Book = {}+jsonDecBook = J.succeed {} -getBooksByTitle : String -> Http.Request (Book)-getBooksByTitle capture_title =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ ""- , "books"- , capture_title |> Http.encodeUri- ]- , body =- Http.emptyBody- , expect =- Http.expectJson decodeBook- , timeout =- Nothing- , withCredentials =- False- }+getBooksByTitle : String -> (Result Http.Error (Book) -> msg) -> Cmd msg+getBooksByTitle capture_title toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "books"+ , capture_title+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg jsonDecBook+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getBooksSource.elm view
@@ -2,30 +2,39 @@ import Http import Json.Decode exposing (..)+import Url.Builder+import Json.Decode as J +type alias Book = {} -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 =+maybeBoolToIntStr : Maybe Bool -> String+maybeBoolToIntStr mx =+ case mx of+ Nothing -> ""+ Just True -> "1"+ Just False -> "0"++jsonDecBook = J.succeed {}++getBooks : Bool -> (Maybe String) -> (Maybe Int) -> String -> (List (Maybe Bool)) -> (Result Http.Error ((List Book)) -> msg) -> Cmd msg+getBooks query_published query_sort query_year query_category query_filters toMsg = let params =- List.filter (not << String.isEmpty)- [ if query_published then- "query_published="+ List.filterMap identity+ (List.concat+ [ [ if query_published then+ Just (Url.Builder.string "published" "") else- ""- , query_sort- |> Maybe.map (Http.encodeUri >> (++) "sort=")- |> Maybe.withDefault ""- , query_year- |> Maybe.map (toString >> Http.encodeUri >> (++) "year=")- |> Maybe.withDefault ""- , Just query_category- |> Maybe.map (Http.encodeUri >> (++) "category=")- |> Maybe.withDefault ""+ Nothing ]+ , [ query_sort+ |> Maybe.map (Url.Builder.string "query_sort") ]+ , [ query_year+ |> Maybe.map (String.fromInt >> Url.Builder.string "query_year") ]+ , [ Just query_category+ |> Maybe.map (Url.Builder.string "query_category") ] , query_filters- |> List.map (\val -> "query_filters[]=" ++ (val |> toString |> Http.encodeUri))- |> String.join "&"- ]+ |> List.map (\val -> Just (Url.Builder.string "filters[]" (maybeBoolToIntStr val)))+ ]) in Http.request { method =@@ -33,20 +42,16 @@ , headers = [] , url =- String.join "/"- [ ""- , "books"+ Url.Builder.crossOrigin ""+ [ "books" ]- ++ if List.isEmpty params then- ""- else- "?" ++ String.join "&" params+ params , body = Http.emptyBody , expect =- Http.expectJson (list decodeBook)+ Http.expectJson toMsg (Json.Decode.list (jsonDecBook)) , timeout = Nothing- , withCredentials =- False+ , tracker =+ Nothing }
test/elm-sources/getNothingSource.elm view
@@ -1,32 +1,36 @@ module GetNothingSource exposing (..) import Http+import Url.Builder -getNothing : Http.Request (NoContent)-getNothing =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ ""- , "nothing"- ]- , body =- Http.emptyBody- , expect =- Http.expectStringResponse- (\{ body } ->- if String.isEmpty body then- Ok NoContent- else- Err "Expected the response body to be empty"- )- , timeout =- Nothing- , withCredentials =- False- }+getNothing : (Result Http.Error (()) -> msg) -> Cmd msg+getNothing toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "nothing"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectString + (\x -> case x of+ Err e -> toMsg (Err e)+ Ok _ -> toMsg (Ok ()))+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getOneSource.elm view
@@ -2,26 +2,33 @@ import Http import Json.Decode exposing (..)+import Url.Builder -getOne : Http.Request (Int)-getOne =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ ""- , "one"- ]- , body =- Http.emptyBody- , expect =- Http.expectJson int- , timeout =- Nothing- , withCredentials =- False- }+getOne : (Result Http.Error (Int) -> msg) -> Cmd msg+getOne toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "one"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg Json.Decode.int+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getOneWithDynamicUrlSource.elm view
@@ -1,27 +1,34 @@ module GetOneWithDynamicUrlSource exposing (..) import Http+import Url.Builder import Json.Decode exposing (..) -getOne : String -> Http.Request (Int)-getOne urlBase =- Http.request- { method =- "GET"- , headers =- []- , url =- String.join "/"- [ urlBase- , "one"- ]- , body =- Http.emptyBody- , expect =- Http.expectJson int- , timeout =- Nothing- , withCredentials =- False- }+getOne : String -> (Result Http.Error (Int) -> msg) -> Cmd msg+getOne urlBase toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.absolute+ [ "one"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg Json.Decode.int+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getWithaheaderSource.elm view
@@ -1,32 +1,39 @@ module GetWithAHeaderSource exposing (..) import Http+import Url.Builder import Json.Decode exposing (..) -getWithaheader : Maybe (String) -> Maybe (Int) -> String -> Int -> Http.Request (String)-getWithaheader header_myStringHeader header_MyIntHeader header_MyRequiredStringHeader header_MyRequiredIntHeader =- Http.request- { method =- "GET"- , headers =+getWithaheader : (Maybe String) -> (Maybe Int) -> String -> Int -> (Result Http.Error (String) -> msg) -> Cmd msg+getWithaheader header_myStringHeader header_MyIntHeader header_MyRequiredStringHeader header_MyRequiredIntHeader toMsg =+ let+ params = 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 "/"- [ ""- , "with-a-header"- ]- , body =- Http.emptyBody- , expect =- Http.expectJson string- , timeout =- Nothing- , withCredentials =- False- }+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ List.filterMap identity+ [ Maybe.map (Http.header "myStringHeader") header_myStringHeader+ , Maybe.map (Http.header "MyIntHeader" << String.fromInt) header_MyIntHeader+ , Maybe.map (Http.header "MyRequiredStringHeader") (Just header_MyRequiredStringHeader)+ , Maybe.map (Http.header "MyRequiredIntHeader" << String.fromInt) (Just header_MyRequiredIntHeader)+ ]+ , url =+ Url.Builder.crossOrigin ""+ [ "with-a-header"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg Json.Decode.string+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/getWitharesponseheaderSource.elm view
@@ -1,27 +1,34 @@ module GetWithAResponseHeaderSource exposing (..) import Http+import Url.Builder 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- }+getWitharesponseheader : (Result Http.Error (String) -> msg) -> Cmd msg+getWitharesponseheader toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "GET"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "with-a-response-header"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectJson toMsg Json.Decode.string+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/postBooksSource.elm view
@@ -1,32 +1,39 @@ module PostBooksSource exposing (..) import Http+import Url.Builder+import Json.Encode as Enc +type alias Book = {}+jsonEncBook = \b -> Enc.object [] -postBooks : Book -> Http.Request (NoContent)-postBooks body =- Http.request- { method =- "POST"- , headers =- []- , url =- String.join "/"- [ ""- , "books"- ]- , body =- Http.jsonBody (encodeBook body)- , expect =- Http.expectStringResponse- (\{ body } ->- if String.isEmpty body then- Ok NoContent- else- Err "Expected the response body to be empty"- )- , timeout =- Nothing- , withCredentials =- False- }+postBooks : Book -> (Result Http.Error (()) -> msg) -> Cmd msg+postBooks body toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "POST"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "books"+ ]+ params+ , body =+ Http.jsonBody (jsonEncBook body)+ , expect =+ Http.expectString + (\x -> case x of+ Err e -> toMsg (Err e)+ Ok _ -> toMsg (Ok ()))+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/postTwoSource.elm view
@@ -3,26 +3,33 @@ import Http import Json.Decode exposing (..) import Json.Encode+import Url.Builder -postTwo : String -> Http.Request (Maybe (Int))-postTwo body =- Http.request- { method =- "POST"- , headers =- []- , url =- String.join "/"- [ ""- , "two"- ]- , body =- Http.jsonBody (Json.Encode.string body)- , expect =- Http.expectJson (maybe int)- , timeout =- Nothing- , withCredentials =- False- }+postTwo : String -> (Result Http.Error ((Maybe Int)) -> msg) -> Cmd msg+postTwo body toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "POST"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "two"+ ]+ params+ , body =+ Http.jsonBody (Json.Encode.string body)+ , expect =+ Http.expectJson toMsg (Json.Decode.maybe (Json.Decode.int))+ , timeout =+ Nothing+ , tracker =+ Nothing+ }
test/elm-sources/putNothingSource.elm view
@@ -1,32 +1,36 @@ module PutNothingSource exposing (..) import Http+import Url.Builder -putNothing : Http.Request (())-putNothing =- Http.request- { method =- "PUT"- , headers =- []- , url =- String.join "/"- [ ""- , "nothing"- ]- , body =- Http.emptyBody- , expect =- Http.expectStringResponse- (\{ body } ->- if String.isEmpty body then- Ok ()- else- Err "Expected the response body to be empty"- )- , timeout =- Nothing- , withCredentials =- False- }+putNothing : (Result Http.Error (()) -> msg) -> Cmd msg+putNothing toMsg =+ let+ params =+ List.filterMap identity+ (List.concat+ [])+ in+ Http.request+ { method =+ "PUT"+ , headers =+ []+ , url =+ Url.Builder.crossOrigin ""+ [ "nothing"+ ]+ params+ , body =+ Http.emptyBody+ , expect =+ Http.expectString + (\x -> case x of+ Err e -> toMsg (Err e)+ Ok _ -> toMsg (Ok ()))+ , timeout =+ Nothing+ , tracker =+ Nothing+ }