servant-kotlin 0.1.1.0 → 0.1.1.1
raw patch · 12 files changed
+958/−957 lines, 12 filesdep ~aesondep ~hspecdep ~http-api-dataPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: aeson, hspec, http-api-data, shelly, wl-pprint-text
API changes (from Hackage documentation)
Files
- LICENSE +21/−21
- README.md +28/−28
- example/Generater.hs +52/−52
- servant-kotlin.cabal +12/−13
- src/Servant/Kotlin.hs +29/−29
- src/Servant/Kotlin/Internal/File.hs +46/−46
- src/Servant/Kotlin/Internal/Foreign.hs +25/−25
- src/Servant/Kotlin/Internal/Generate.hs +364/−362
- src/Servant/Kotlin/Type.hs +185/−185
- test/Servant/Kotlin/Internal/GenerateSpec.hs +140/−140
- test/Spec.hs +1/−1
- test/Test/TestAPI.hs +55/−55
LICENSE view
@@ -1,21 +1,21 @@-MIT License - -Copyright (c) 2017-2018 MATSUBARA Nobutada - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License++Copyright (c) 2017-2018 MATSUBARA Nobutada++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
README.md view
@@ -1,28 +1,28 @@-# Servant Kotlin - -[](https://hackage.haskell.org/package/servant-kotlin) -[](https://travis-ci.org/matsubara0507/servant-kotlin) -[](http://stackage.org/lts/package/servant-kotlin) -[](http://stackage.org/nightly/package/servant-kotlin) - -Generate Kotlin class to query your Servant API! - -## Example - -see [`example/Generater.hs`](/example/Generater.hs). - -this file generate [`TodoAPI.kt`](/example/com/github/matsubara0507/TodoAPI.kt). - -## dependencies for Kotlin - -- [kittinunf/Fuel](https://github.com/kittinunf/Fuel): The easiest HTTP networking library for Kotlin/Android -- [google/gson](https://github.com/google/gson): A Java serialization/deserialization library to convert Java Objects into JSON and back - -## not yet, no Implements - -- [ ] Original QueryFlag -- [ ] Original Header - -## Acknowledgement - -This package is greatly inspired by [`elm-export`](https://hackage.haskell.org/package/elm-export) and [`servant-elm`](https://hackage.haskell.org/package/servant-elm). +# Servant Kotlin++[](https://hackage.haskell.org/package/servant-kotlin)+[](https://travis-ci.org/matsubara0507/servant-kotlin)+[](http://stackage.org/lts/package/servant-kotlin)+[](http://stackage.org/nightly/package/servant-kotlin)++Generate Kotlin class to query your Servant API!++## Example++see [`example/Generater.hs`](/example/Generater.hs).++this file generate [`TodoAPI.kt`](/example/com/github/matsubara0507/TodoAPI.kt).++## dependencies for Kotlin++- [kittinunf/Fuel](https://github.com/kittinunf/Fuel): The easiest HTTP networking library for Kotlin/Android+- [google/gson](https://github.com/google/gson): A Java serialization/deserialization library to convert Java Objects into JSON and back++## not yet, no Implements++- [ ] Original QueryFlag+- [ ] Original Header++## Acknowledgement++This package is greatly inspired by [`elm-export`](https://hackage.haskell.org/package/elm-export) and [`servant-elm`](https://hackage.haskell.org/package/servant-elm).
example/Generater.hs view
@@ -1,52 +1,52 @@-{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeOperators #-} - -module Main where - -import Data.Aeson (FromJSON, ToJSON) -import Data.Proxy (Proxy (Proxy)) -import Data.Text (Text) -import GHC.Generics (Generic) -import Servant.API ((:<|>) (..), (:>), Capture, - Delete, FormUrlEncoded, Get, JSON, - Post, Put, ReqBody) -import Servant.Kotlin -import Shelly (cd, run_, shelly, which) -import Web.Internal.FormUrlEncoded (FromForm) - -data Todo = Todo - { todoId :: Int - , title :: Text - , done :: Bool - } deriving (Generic, Show, Eq, KotlinType) - -instance FromJSON Todo -instance ToJSON Todo -instance FromForm Todo - -type CRUD = "todos" :> Get '[JSON] [Todo] - :<|> "todos" :> ReqBody '[JSON, FormUrlEncoded] Todo :> Post '[JSON] Todo - :<|> "todos" :> Capture "id" Int :> ReqBody '[JSON, FormUrlEncoded] Todo :> Put '[JSON] () - :<|> "todos" :> Capture "id" Int :> Delete '[JSON] () - -body :: [Text] -body = mconcat - [ [ defKotlinImports ] - , generateKotlinForAPIClass "TodoAPI" $ mconcat - [ generateKotlinForDefDataClass (Proxy :: Proxy Todo) - , generateKotlinForAPI (Proxy :: Proxy CRUD) - ] - ] - -spec :: Spec -spec = Spec ["com", "github", "matsubara0507"] "TodoAPI" body - -main :: IO () -main = do - specsToDir [spec] "example/src/main/java" - shelly $ do - cd "example" - which "gradle" >>= mapM_ (\_ -> run_ "gradle" ["build"]) +{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}++module Main where++import Data.Aeson (FromJSON, ToJSON)+import Data.Proxy (Proxy (Proxy))+import Data.Text (Text)+import GHC.Generics (Generic)+import Servant.API ((:<|>) (..), (:>), Capture,+ Delete, FormUrlEncoded, Get, JSON,+ Post, Put, ReqBody)+import Servant.Kotlin+import Shelly (cd, run_, shelly, which)+import Web.Internal.FormUrlEncoded (FromForm)++data Todo = Todo+ { todoId :: Int+ , title :: Text+ , done :: Bool+ } deriving (Generic, Show, Eq, KotlinType)++instance FromJSON Todo+instance ToJSON Todo+instance FromForm Todo++type CRUD = "todos" :> Get '[JSON] [Todo]+ :<|> "todos" :> ReqBody '[JSON, FormUrlEncoded] Todo :> Post '[JSON] Todo+ :<|> "todos" :> Capture "id" Int :> ReqBody '[JSON, FormUrlEncoded] Todo :> Put '[JSON] ()+ :<|> "todos" :> Capture "id" Int :> Delete '[JSON] ()++body :: [Text]+body = mconcat+ [ [ defKotlinImports ]+ , generateKotlinForAPIClass "TodoAPI" $ mconcat+ [ generateKotlinForDefDataClass (Proxy :: Proxy Todo)+ , generateKotlinForAPI (Proxy :: Proxy CRUD)+ ]+ ]++spec :: Spec+spec = Spec ["com", "github", "matsubara0507"] "TodoAPI" body++main :: IO ()+main = do+ specsToDir [spec] "example/src/main/java"+ shelly $ do+ cd "example"+ which "gradle" >>= mapM_ (\_ -> run_ "gradle" ["build"])
servant-kotlin.cabal view
@@ -1,11 +1,11 @@--- This file has been generated from package.yaml by hpack version 0.20.0.+-- This file has been generated from package.yaml by hpack version 0.28.2. -- -- see: https://github.com/sol/hpack ----- hash: 72d6da8fcfbeeb254805cbaf73c72da019a8ab6a31d9b651dd95eaadb6be1443+-- hash: adcb0ee175174fe1ee5718b11a866be2613b0763a25715055ac3e9fa4c4ac69e name: servant-kotlin-version: 0.1.1.0+version: 0.1.1.1 synopsis: Automatically derive Kotlin class to query servant webservices description: See README at <https://github.com/matsubara0507/servant-kotlin#readme> category: Web@@ -17,7 +17,6 @@ license-file: LICENSE build-type: Simple cabal-version: >= 1.10- extra-source-files: README.md @@ -35,7 +34,7 @@ , servant-foreign >=0.9 && <0.12 , text >=1.2 && <1.3 , time >=1.6 && <1.9- , wl-pprint-text >=1.1 && <1.2+ , wl-pprint-text >=1.1 && <1.3 exposed-modules: Servant.Kotlin Servant.Kotlin.Internal.File@@ -54,19 +53,19 @@ test ghc-options: -Wall build-depends:- aeson >=1.0 && <1.3+ aeson >=1.0 && <1.4 , base >=4.7 && <5 , containers >=0.5.7 && <0.6.0 , directory >=1.3 && <1.4 , formatting >=6.2 && <6.4- , hspec >=2.4.1 && <2.5- , http-api-data >=0.3.7 && <0.3.8+ , hspec >=2.4.1 && <2.6+ , http-api-data >=0.3.7 && <0.3.9 , lens >=4.15 && <4.17 , servant >=0.9 && <0.14 , servant-foreign >=0.9 && <0.12 , text >=1.2 && <1.3 , time >=1.6 && <1.9- , wl-pprint-text >=1.1 && <1.2+ , wl-pprint-text >=1.1 && <1.3 other-modules: Servant.Kotlin Servant.Kotlin.Internal.File@@ -83,20 +82,20 @@ main-is: example/Generater.hs ghc-options: -Wall build-depends:- aeson >=1.0 && <1.3+ aeson >=1.0 && <1.4 , base >=4.7 && <5 , containers >=0.5.7 && <0.6.0 , directory >=1.3 && <1.4 , formatting >=6.2 && <6.4- , http-api-data >=0.3.7 && <0.3.8+ , http-api-data >=0.3.7 && <0.3.9 , lens >=4.15 && <4.17 , servant >=0.9 && <0.14 , servant-foreign >=0.9 && <0.12 , servant-kotlin- , shelly >=1.6.8 && <1.8+ , shelly >=1.6.8 && <1.9 , text >=1.2 && <1.3 , time >=1.6 && <1.9- , wl-pprint-text >=1.1 && <1.2+ , wl-pprint-text >=1.1 && <1.3 other-modules: Paths_servant_kotlin default-language: Haskell2010
src/Servant/Kotlin.hs view
@@ -1,29 +1,29 @@-module Servant.Kotlin - ( generateKotlinForDefDataClass - , defKotlinImports - , generateKotlinForAPI - , generateKotlinForAPIWith - , generateKotlinForAPIClass - , KotlinOptions(..) - , UrlPrefix(..) - , defKotlinOptions - , Spec (..) - , specsToDir - -- * Convenience re-exports from the "Kotlin" module - , KotlinType - -- * Convenience re-exports from "Data.Proxy" - , Proxy(Proxy) - ) where - -import Servant.Kotlin.Internal.File (Spec (..), specsToDir) -import Servant.Kotlin.Internal.Generate (KotlinOptions (..), - UrlPrefix (..), - defKotlinImports, - defKotlinOptions, - generateKotlinForAPI, - generateKotlinForAPIClass, - generateKotlinForAPIWith, - generateKotlinForDefDataClass) - -import Data.Proxy (Proxy (Proxy)) -import Servant.Kotlin.Type (KotlinType) +module Servant.Kotlin+ ( generateKotlinForDefDataClass+ , defKotlinImports+ , generateKotlinForAPI+ , generateKotlinForAPIWith+ , generateKotlinForAPIClass+ , KotlinOptions(..)+ , UrlPrefix(..)+ , defKotlinOptions+ , Spec (..)+ , specsToDir+ -- * Convenience re-exports from the "Kotlin" module+ , KotlinType+ -- * Convenience re-exports from "Data.Proxy"+ , Proxy(Proxy)+ ) where++import Servant.Kotlin.Internal.File (Spec (..), specsToDir)+import Servant.Kotlin.Internal.Generate (KotlinOptions (..),+ UrlPrefix (..),+ defKotlinImports,+ defKotlinOptions,+ generateKotlinForAPI,+ generateKotlinForAPIClass,+ generateKotlinForAPIWith,+ generateKotlinForDefDataClass)++import Data.Proxy (Proxy (Proxy))+import Servant.Kotlin.Type (KotlinType)
src/Servant/Kotlin/Internal/File.hs view
@@ -1,46 +1,46 @@-{-# LANGUAGE OverloadedStrings #-} - -module Servant.Kotlin.Internal.File - ( Spec (..) - , specsToDir - ) where - -import Data.Monoid ((<>)) -import Data.Text (Text, pack, unpack) -import qualified Data.Text as Text -import qualified Data.Text.IO as Text -import Formatting as F -import System.Directory (createDirectoryIfMissing) - -makePath :: [Text] -> Text -makePath = Text.intercalate "/" - -data Spec = Spec - { namespace :: [Text] - , filename :: Text - , declarations :: [Text] - } deriving (Show) - -pathForSpec :: FilePath -> Spec -> [Text] -pathForSpec rootDir spec = pack rootDir : namespace spec <> [filename spec] - -ensureDirectory :: FilePath -> Spec -> IO () -ensureDirectory rootDir spec = createDirectoryIfMissing True $ unpack dir - where - dir = makePath . init $ pathForSpec rootDir spec - -specToFile :: FilePath -> Spec -> IO () -specToFile rootDir spec = do - fprint ("Writing: " % F.stext % "\n") file - Text.writeFile (unpack file) body - where - path = pathForSpec rootDir spec - file = makePath path <> ".kt" - namespaceText = Text.intercalate "." (namespace spec) - body = Text.intercalate "\n\n" $ - "package " <> namespaceText : declarations spec - -specsToDir :: [Spec] -> FilePath -> IO () -specsToDir specs rootDir = mapM_ processSpec specs - where - processSpec = ensureDirectory rootDir >> specToFile rootDir +{-# LANGUAGE OverloadedStrings #-}++module Servant.Kotlin.Internal.File+ ( Spec (..)+ , specsToDir+ ) where++import Data.Monoid ((<>))+import Data.Text (Text, pack, unpack)+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import Formatting as F+import System.Directory (createDirectoryIfMissing)++makePath :: [Text] -> Text+makePath = Text.intercalate "/"++data Spec = Spec+ { namespace :: [Text]+ , filename :: Text+ , declarations :: [Text]+ } deriving (Show)++pathForSpec :: FilePath -> Spec -> [Text]+pathForSpec rootDir spec = pack rootDir : namespace spec <> [filename spec]++ensureDirectory :: FilePath -> Spec -> IO ()+ensureDirectory rootDir spec = createDirectoryIfMissing True $ unpack dir+ where+ dir = makePath . init $ pathForSpec rootDir spec++specToFile :: FilePath -> Spec -> IO ()+specToFile rootDir spec = do+ fprint ("Writing: " % F.stext % "\n") file+ Text.writeFile (unpack file) body+ where+ path = pathForSpec rootDir spec+ file = makePath path <> ".kt"+ namespaceText = Text.intercalate "." (namespace spec)+ body = Text.intercalate "\n\n" $+ "package " <> namespaceText : declarations spec++specsToDir :: [Spec] -> FilePath -> IO ()+specsToDir specs rootDir = mapM_ processSpec specs+ where+ processSpec = ensureDirectory rootDir >> specToFile rootDir
src/Servant/Kotlin/Internal/Foreign.hs view
@@ -1,25 +1,25 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} - -module Servant.Kotlin.Internal.Foreign where - -import Data.Proxy (Proxy (Proxy)) -import Servant.Foreign (Foreign, GenerateList, HasForeign, - HasForeignType, Req, listFromAPI, typeFor) -import Servant.Kotlin.Type (KotlinClass, KotlinType, toKotlinType') - -data LangKotlin - -instance (KotlinType a) => HasForeignType LangKotlin KotlinClass a where - typeFor _ _ _ = toKotlinType' (Proxy :: Proxy a) - -getEndpoints :: - ( HasForeign LangKotlin KotlinClass api - , GenerateList KotlinClass (Foreign KotlinClass api)) - => Proxy api - -> [Req KotlinClass] -getEndpoints = - listFromAPI (Proxy :: Proxy LangKotlin) (Proxy :: Proxy KotlinClass) +{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++module Servant.Kotlin.Internal.Foreign where++import Data.Proxy (Proxy (Proxy))+import Servant.Foreign (Foreign, GenerateList, HasForeign,+ HasForeignType, Req, listFromAPI, typeFor)+import Servant.Kotlin.Type (KotlinClass, KotlinType, toKotlinType')++data LangKotlin++instance (KotlinType a) => HasForeignType LangKotlin KotlinClass a where+ typeFor _ _ _ = toKotlinType' (Proxy :: Proxy a)++getEndpoints ::+ ( HasForeign LangKotlin KotlinClass api+ , GenerateList KotlinClass (Foreign KotlinClass api))+ => Proxy api+ -> [Req KotlinClass]+getEndpoints =+ listFromAPI (Proxy :: Proxy LangKotlin) (Proxy :: Proxy KotlinClass)
src/Servant/Kotlin/Internal/Generate.hs view
@@ -1,362 +1,364 @@-{-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} - -module Servant.Kotlin.Internal.Generate - ( GenerateKotlin (..) - , generateKotlinForDefDataClass - , generateKotlinForDefDataClass' - , defKotlinImports - , generateKotlinForAPIClass - , generateKotlinForAPI - , generateKotlinForAPIWith - , KotlinOptions (..) - , defKotlinOptions - , UrlPrefix (..) - ) where - -import Control.Lens (to, (^.)) -import Data.List (nub) -import Data.Maybe (catMaybes, fromMaybe) -import Data.Monoid ((<>)) -import Data.Proxy (Proxy) -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import qualified Data.Text.Lazy as L -import Servant.API (NoContent (..)) -import qualified Servant.Foreign as F -import Servant.Kotlin.Internal.Foreign (LangKotlin, getEndpoints) -import Servant.Kotlin.Type -import Text.PrettyPrint.Leijen.Text hiding ((<>)) - -class GenerateKotlin a where - generateKotlin :: a -> [Text] - generateKotlin' :: a -> Text - generateKotlin' = T.concat . generateKotlin - -instance GenerateKotlin KotlinClass where - generateKotlin (PrimitiveClass c) = generateKotlin c - generateKotlin (ExClass c) = generateKotlin c - generateKotlin (DataClass (KotlinDataClass name _)) = [name] - -instance GenerateKotlin KotlinPrimitiveClass where - generateKotlin KDouble = ["Double"] - generateKotlin KFloat = ["Float"] - generateKotlin KLong = ["Long"] - generateKotlin KInt = ["Int"] - generateKotlin KShort = ["Short"] - generateKotlin KByte = ["Byte"] - generateKotlin KChar = ["Char"] - generateKotlin KBoolean = ["Boolean"] - generateKotlin (KArray c) = ["Array<" <> generateKotlin' c <> ">"] - generateKotlin KString = ["String"] - generateKotlin KUnit = ["Unit"] - generateKotlin (KNullable c) = [generateKotlin' c <> "?"] - generateKotlin KAny = ["Any"] - -instance GenerateKotlin KotlinExClass where - generateKotlin (KList c) = - ["List<" <> generateKotlin' c <> ">"] - generateKotlin (KHashMap k v) = - ["HashMap<" <> generateKotlin' k <> ", " <> generateKotlin' v <> ">"] - generateKotlin (KPair a b) = - ["Pair<" <> generateKotlin' a <> ", " <> generateKotlin' b <> ">"] - generateKotlin KTime = ["Time"] - -instance GenerateKotlin KotlinDataClass where - generateKotlin (KotlinDataClass name fields) = - [ "data class " <> name <> "(" <> generateKotlin' fields <> ")" ] - -instance GenerateKotlin KotlinFields where - generateKotlin (Node field) = generateKotlin field - generateKotlin (Brunch a b) = [generateKotlin' a <> ", " <> generateKotlin' b] - -instance GenerateKotlin KotlinField where - generateKotlin (KotlinField name c) = - ["val " <> name <> ": " <> generateKotlin' c] - -generateKotlinForDefDataClass' :: KotlinClass -> [Text] -generateKotlinForDefDataClass' (DataClass c) = generateKotlin c -generateKotlinForDefDataClass' _ = [] - -generateKotlinForDefDataClass :: (KotlinType a) => Proxy a -> [Text] -generateKotlinForDefDataClass = - maybe [] generateKotlinForDefDataClass' . toKotlinType - ---- - -defKotlinImports :: Text -defKotlinImports = docToText . vsep $ fmap ("import" <+>) - [ "com.github.kittinunf.fuel.Fuel" - , "com.github.kittinunf.fuel.core.FuelError" - , "com.github.kittinunf.fuel.core.FuelManager" - , "com.github.kittinunf.fuel.core.Request" - , "com.github.kittinunf.fuel.core.Response" - , "com.github.kittinunf.fuel.gson.responseObject" - , "com.github.kittinunf.result.Result" - , "com.google.gson.Gson" - ] - ---- - -generateKotlinForAPIClass :: Text -> [Text] -> [Text] -generateKotlinForAPIClass className body = mconcat - [ [ docToText $ "class" <+> textStrict className <> "(private val baseURL: String) {" ] - , [ docToText $ indent indentNum initialize ] - , fmap (docToText . vsep . fmap (indent indentNum . textStrict) . T.lines) body - , [ "}" ] - ] - where - initialize = vsep [ "init {", indent indentNum fuelManager, "}" ] - fuelManager = vsep - [ "FuelManager.instance.apply {" - , indent indentNum "basePath = baseURL" - , indent indentNum $ "baseHeaders = mapOf(" <> header <> ")" - , "}" - ] - header = hsep . punctuate comma $ - fmap (\(k, v) -> dquotes k <+> "to" <+> dquotes v) - [("Content-Type", "application/json"), ("Device", "Android")] - ---- - -{-| - Generate Kotlin code for the API with default options. - Returns a list of Kotlin functions to query your Servant API from Kotlin. --} -generateKotlinForAPI :: - ( F.HasForeign LangKotlin KotlinClass api - , F.GenerateList KotlinClass (F.Foreign KotlinClass api)) - => Proxy api - -> [Text] -generateKotlinForAPI = - generateKotlinForAPIWith defKotlinOptions - -{-| - Generate Kotlin code for the API with custom options. --} -generateKotlinForAPIWith :: - ( F.HasForeign LangKotlin KotlinClass api - , F.GenerateList KotlinClass (F.Foreign KotlinClass api)) - => KotlinOptions - -> Proxy api - -> [Text] -generateKotlinForAPIWith opts = - nub . fmap (docToText . generateKotlinForRequest opts) . getEndpoints - -indentNum :: Int -indentNum = 4 - -{-| - Generate an Kotlin function for one endpoint. --} -generateKotlinForRequest :: KotlinOptions -> F.Req KotlinClass -> Doc -generateKotlinForRequest opts request = funcDef - where - funcDef = - vsep - [ "fun" <+> fnName <> "(" <> args <> ") {" - , indent indentNum kotlinRequest - , "}" - ] - - fnName = - request ^. F.reqFuncName . to (T.replace "-" "" . F.camelCase) . to stext - - args = - mkArgs opts request - - kotlinRequest = - mkRequest opts request - -mkArgs :: KotlinOptions -> F.Req KotlinClass -> Doc -mkArgs opts request = - (hsep . punctuate comma . concat) - [ urlPrefixArg - , headerArgs - , urlCaptureArgs - , queryArgs - , requestBodyArg - , handlerArg - ] - where - urlPrefixArg :: [Doc] - urlPrefixArg = - case urlPrefix opts of - Dynamic -> ["urlBase: String"] - Static _ -> [] - - headerArgs :: [Doc] - headerArgs = - [ kotlinHeaderArg header <> ": " <> kotlinHeaderType header - | header <- request ^. F.reqHeaders - ] - - urlCaptureArgs :: [Doc] - urlCaptureArgs = - [ kotlinCaptureArg segment <> ": " <> kotlinCaptureType segment - | segment <- request ^. F.reqUrl . F.path, F.isCapture segment - ] - - queryArgs :: [Doc] - queryArgs = - [ kotlinQueryArg arg <> ": " <> kotlinQueryType arg - | arg <- request ^. F.reqUrl . F.queryStr - ] - - requestBodyArg :: [Doc] - requestBodyArg = - maybe [] (\body -> [kotlinBodyArg <> ": " <> kotlinTypeRef body]) $ - request ^. F.reqBody - - handlerArg :: [Doc] - handlerArg = [kotlinHandlerArg <> ": " <> handlerType] - where - handlerType = - "(Request, Response, Result<" <> returnType <> ", FuelError>) -> Unit" - - returnType :: Doc - returnType = kotlinTypeRef . - fromMaybe (PrimitiveClass KUnit) $ request ^. F.reqReturnType - -kotlinHeaderArg :: F.HeaderArg KotlinClass -> Doc -kotlinHeaderArg header = "header_" <> - header ^. F.headerArg . F.argName - . to (stext . T.replace "-" "_" . F.unPathSegment) - -kotlinHeaderType :: F.HeaderArg KotlinClass -> Doc -#if MIN_VERSION_servant_foreign(0,11,0) -kotlinHeaderType header = - header ^. F.headerArg . F.argType . to kotlinTypeRef -#else -kotlinHeaderType header = - header ^. F.headerArg . F.argType . to (kotlinTypeRef . wrapper) - where - wrapper = PrimitiveClass . KNullable -#endif - -kotlinCaptureArg :: F.Segment KotlinClass -> Doc -kotlinCaptureArg segment = "capture_" <> - F.captureArg segment ^. F.argName . to (stext . F.unPathSegment) - -kotlinCaptureType :: F.Segment KotlinClass -> Doc -kotlinCaptureType segment = - F.captureArg segment ^. F.argType . to kotlinTypeRef - -kotlinQueryArg :: F.QueryArg KotlinClass -> Doc -kotlinQueryArg arg = "query_" <> - arg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment) - -kotlinQueryType :: F.QueryArg KotlinClass -> Doc -#if MIN_VERSION_servant_foreign(0,11,0) -kotlinQueryType arg = - arg ^. F.queryArgName . F.argType . to kotlinTypeRef -#else -kotlinQueryType arg = - arg ^. F.queryArgName . F.argType . to (kotlinTypeRef . wrapper) - where - wrapper = case arg ^. F.queryArgType of - F.Normal -> PrimitiveClass . KNullable - _ -> id -#endif - -kotlinBodyArg :: Doc -kotlinBodyArg = "body" - -kotlinHandlerArg :: Doc -kotlinHandlerArg = "handler" - -kotlinTypeRef :: KotlinClass -> Doc -kotlinTypeRef = stext . generateKotlin' - - -mkRequest :: KotlinOptions -> F.Req KotlinClass -> Doc -mkRequest opts request = "Fuel" <> align (vsep methodChain) - where - methodChain = catMaybes - [ Just $ mconcat [".", method, "(", url, ")"] - , body - , Just ".responseObject(handler)" - ] - - method = - request ^. F.reqMethod . to (stext . T.toLower . T.decodeUtf8) - - url = - mkUrl opts (request ^. F.reqUrl . F.path) <> mkQueryParams request - - body = fmap (\b -> mconcat - [ ".body(Gson().toJson(" - , kotlinBodyArg - , ", " - , kotlinTypeRef b - , "::class.java))" - ] - ) $ request ^. F.reqBody - - -mkUrl :: KotlinOptions -> [F.Segment KotlinClass] -> Doc -mkUrl _opts segments = mconcat . punctuate " + " $ - dquotes "/" : punctuate (" + " <> dquotes "/") (map segmentToDoc segments) - where - segmentToDoc :: F.Segment KotlinClass -> Doc - segmentToDoc segment = - case F.unSegment segment of - F.Static path -> dquotes (stext (F.unPathSegment path)) - F.Cap _arg -> kotlinCaptureArg segment - --- TODO: implements -mkQueryParams :: F.Req KotlinClass -> Doc -mkQueryParams _request = "" - -- if null (request ^. F.reqUrl . F.queryStr) then - -- empty - -- else - -- " +" <+> dquotes "?" <+> "+" <+> - -- "params.joinToString(" <> dquotes "&" <> ")" - -{-| - Options to configure how code is generated. --} -data KotlinOptions = KotlinOptions - { urlPrefix :: UrlPrefix - , emptyResponseKotlinTypes :: [KotlinClass] - -- ^ Types that represent an empty Http response. - , stringKotlinTypes :: [KotlinClass] - -- ^ Types that represent a String. - } - -data UrlPrefix - = Static Text - | Dynamic - -{-| - Default options for generating Kotlin code. - The default options are: - > { urlPrefix = Static "" - > , emptyResponseKotlinTypes = [ toKotlinType NoContent ] - > , stringKotlinTypes = [ toKotlinType "" ] - > } --} -defKotlinOptions :: KotlinOptions -defKotlinOptions = KotlinOptions - { urlPrefix = Static "" - , emptyResponseKotlinTypes = - [ toKotlinType' NoContent - , toKotlinType' () - ] - , stringKotlinTypes = - [ toKotlinType' ("" :: String) - , toKotlinType' ("" :: Text) - ] - } - ---- - -docToText :: Doc -> Text -docToText = - L.toStrict . displayT . renderPretty 0.4 100 - -stext :: Text -> Doc -stext = text . L.fromStrict +{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++module Servant.Kotlin.Internal.Generate+ ( GenerateKotlin (..)+ , generateKotlinForDefDataClass+ , generateKotlinForDefDataClass'+ , defKotlinImports+ , generateKotlinForAPIClass+ , generateKotlinForAPI+ , generateKotlinForAPIWith+ , KotlinOptions (..)+ , defKotlinOptions+ , UrlPrefix (..)+ ) where++import Control.Lens (to, (^.))+import Data.List (nub)+import Data.Maybe (catMaybes, fromMaybe)+import Data.Monoid ((<>))+import Data.Proxy (Proxy)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as L+import Servant.API (NoContent (..))+import qualified Servant.Foreign as F+import Servant.Kotlin.Internal.Foreign (LangKotlin, getEndpoints)+import Servant.Kotlin.Type+import Text.PrettyPrint.Leijen.Text (Doc, (<+>))+import qualified Text.PrettyPrint.Leijen.Text as PP++class GenerateKotlin a where+ generateKotlin :: a -> [Text]+ generateKotlin' :: a -> Text+ generateKotlin' = T.concat . generateKotlin++instance GenerateKotlin KotlinClass where+ generateKotlin (PrimitiveClass c) = generateKotlin c+ generateKotlin (ExClass c) = generateKotlin c+ generateKotlin (DataClass (KotlinDataClass name _)) = [name]++instance GenerateKotlin KotlinPrimitiveClass where+ generateKotlin KDouble = ["Double"]+ generateKotlin KFloat = ["Float"]+ generateKotlin KLong = ["Long"]+ generateKotlin KInt = ["Int"]+ generateKotlin KShort = ["Short"]+ generateKotlin KByte = ["Byte"]+ generateKotlin KChar = ["Char"]+ generateKotlin KBoolean = ["Boolean"]+ generateKotlin (KArray c) = ["Array<" <> generateKotlin' c <> ">"]+ generateKotlin KString = ["String"]+ generateKotlin KUnit = ["Unit"]+ generateKotlin (KNullable c) = [generateKotlin' c <> "?"]+ generateKotlin KAny = ["Any"]++instance GenerateKotlin KotlinExClass where+ generateKotlin (KList c) =+ ["List<" <> generateKotlin' c <> ">"]+ generateKotlin (KHashMap k v) =+ ["HashMap<" <> generateKotlin' k <> ", " <> generateKotlin' v <> ">"]+ generateKotlin (KPair a b) =+ ["Pair<" <> generateKotlin' a <> ", " <> generateKotlin' b <> ">"]+ generateKotlin KTime = ["Time"]++instance GenerateKotlin KotlinDataClass where+ generateKotlin (KotlinDataClass name fields) =+ [ "data class " <> name <> "(" <> generateKotlin' fields <> ")" ]++instance GenerateKotlin KotlinFields where+ generateKotlin (Node field) = generateKotlin field+ generateKotlin (Brunch a b) = [generateKotlin' a <> ", " <> generateKotlin' b]++instance GenerateKotlin KotlinField where+ generateKotlin (KotlinField name c) =+ ["val " <> name <> ": " <> generateKotlin' c]++generateKotlinForDefDataClass' :: KotlinClass -> [Text]+generateKotlinForDefDataClass' (DataClass c) = generateKotlin c+generateKotlinForDefDataClass' _ = []++generateKotlinForDefDataClass :: (KotlinType a) => Proxy a -> [Text]+generateKotlinForDefDataClass =+ maybe [] generateKotlinForDefDataClass' . toKotlinType++---++defKotlinImports :: Text+defKotlinImports = docToText . PP.vsep $ fmap ("import" <+>)+ [ "com.github.kittinunf.fuel.Fuel"+ , "com.github.kittinunf.fuel.core.FuelError"+ , "com.github.kittinunf.fuel.core.FuelManager"+ , "com.github.kittinunf.fuel.core.Request"+ , "com.github.kittinunf.fuel.core.Response"+ , "com.github.kittinunf.fuel.gson.responseObject"+ , "com.github.kittinunf.result.Result"+ , "com.google.gson.Gson"+ ]++---++generateKotlinForAPIClass :: Text -> [Text] -> [Text]+generateKotlinForAPIClass className body = mconcat+ [ [ docToText $ "class" <+> PP.textStrict className <> "(private val baseURL: String) {" ]+ , [ docToText $ PP.indent indentNum initialize ]+ , fmap (docToText . PP.vsep . fmap (PP.indent indentNum . PP.textStrict) . T.lines) body+ , [ "}" ]+ ]+ where+ initialize = PP.vsep [ "init {", PP.indent indentNum fuelManager, "}" ]+ fuelManager = PP.vsep+ [ "FuelManager.instance.apply {"+ , PP.indent indentNum "basePath = baseURL"+ , PP.indent indentNum $ "baseHeaders = mapOf(" <> header <> ")"+ , "}"+ ]+ header = PP.hsep . PP.punctuate PP.comma $+ fmap (\(k, v) -> PP.dquotes k <+> "to" <+> PP.dquotes v)+ [("Content-Type", "application/json"), ("Device", "Android")]++---++{-|+ Generate Kotlin code for the API with default options.+ Returns a list of Kotlin functions to query your Servant API from Kotlin.+-}+generateKotlinForAPI ::+ ( F.HasForeign LangKotlin KotlinClass api+ , F.GenerateList KotlinClass (F.Foreign KotlinClass api))+ => Proxy api+ -> [Text]+generateKotlinForAPI =+ generateKotlinForAPIWith defKotlinOptions++{-|+ Generate Kotlin code for the API with custom options.+-}+generateKotlinForAPIWith ::+ ( F.HasForeign LangKotlin KotlinClass api+ , F.GenerateList KotlinClass (F.Foreign KotlinClass api))+ => KotlinOptions+ -> Proxy api+ -> [Text]+generateKotlinForAPIWith opts =+ nub . fmap (docToText . generateKotlinForRequest opts) . getEndpoints++indentNum :: Int+indentNum = 4++{-|+ Generate an Kotlin function for one endpoint.+-}+generateKotlinForRequest :: KotlinOptions -> F.Req KotlinClass -> Doc+generateKotlinForRequest opts request = funcDef+ where+ funcDef =+ PP.vsep+ [ "fun" <+> fnName <> "(" <> args <> ") {"+ , PP.indent indentNum kotlinRequest+ , "}"+ ]++ fnName =+ request ^. F.reqFuncName . to (T.replace "-" "" . F.camelCase) . to stext++ args =+ mkArgs opts request++ kotlinRequest =+ mkRequest opts request++mkArgs :: KotlinOptions -> F.Req KotlinClass -> Doc+mkArgs opts request =+ (PP.hsep . PP.punctuate PP.comma . concat)+ [ urlPrefixArg+ , headerArgs+ , urlCaptureArgs+ , queryArgs+ , requestBodyArg+ , handlerArg+ ]+ where+ urlPrefixArg :: [Doc]+ urlPrefixArg =+ case urlPrefix opts of+ Dynamic -> ["urlBase: String"]+ Static _ -> []++ headerArgs :: [Doc]+ headerArgs =+ [ kotlinHeaderArg header <> ": " <> kotlinHeaderType header+ | header <- request ^. F.reqHeaders+ ]++ urlCaptureArgs :: [Doc]+ urlCaptureArgs =+ [ kotlinCaptureArg segment <> ": " <> kotlinCaptureType segment+ | segment <- request ^. F.reqUrl . F.path, F.isCapture segment+ ]++ queryArgs :: [Doc]+ queryArgs =+ [ kotlinQueryArg arg <> ": " <> kotlinQueryType arg+ | arg <- request ^. F.reqUrl . F.queryStr+ ]++ requestBodyArg :: [Doc]+ requestBodyArg =+ maybe [] (\body -> [kotlinBodyArg <> ": " <> kotlinTypeRef body]) $+ request ^. F.reqBody++ handlerArg :: [Doc]+ handlerArg = [kotlinHandlerArg <> ": " <> handlerType]+ where+ handlerType =+ "(Request, Response, Result<" <> returnType <> ", FuelError>) -> Unit"++ returnType :: Doc+ returnType = kotlinTypeRef .+ fromMaybe (PrimitiveClass KUnit) $ request ^. F.reqReturnType++kotlinHeaderArg :: F.HeaderArg KotlinClass -> Doc+kotlinHeaderArg header = "header_" <>+ header ^. F.headerArg . F.argName+ . to (stext . T.replace "-" "_" . F.unPathSegment)++kotlinHeaderType :: F.HeaderArg KotlinClass -> Doc+#if MIN_VERSION_servant_foreign(0,11,0)+kotlinHeaderType header =+ header ^. F.headerArg . F.argType . to kotlinTypeRef+#else+kotlinHeaderType header =+ header ^. F.headerArg . F.argType . to (kotlinTypeRef . wrapper)+ where+ wrapper = PrimitiveClass . KNullable+#endif++kotlinCaptureArg :: F.Segment KotlinClass -> Doc+kotlinCaptureArg segment = "capture_" <>+ F.captureArg segment ^. F.argName . to (stext . F.unPathSegment)++kotlinCaptureType :: F.Segment KotlinClass -> Doc+kotlinCaptureType segment =+ F.captureArg segment ^. F.argType . to kotlinTypeRef++kotlinQueryArg :: F.QueryArg KotlinClass -> Doc+kotlinQueryArg arg = "query_" <>+ arg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment)++kotlinQueryType :: F.QueryArg KotlinClass -> Doc+#if MIN_VERSION_servant_foreign(0,11,0)+kotlinQueryType arg =+ arg ^. F.queryArgName . F.argType . to kotlinTypeRef+#else+kotlinQueryType arg =+ arg ^. F.queryArgName . F.argType . to (kotlinTypeRef . wrapper)+ where+ wrapper = case arg ^. F.queryArgType of+ F.Normal -> PrimitiveClass . KNullable+ _ -> id+#endif++kotlinBodyArg :: Doc+kotlinBodyArg = "body"++kotlinHandlerArg :: Doc+kotlinHandlerArg = "handler"++kotlinTypeRef :: KotlinClass -> Doc+kotlinTypeRef = stext . generateKotlin'+++mkRequest :: KotlinOptions -> F.Req KotlinClass -> Doc+mkRequest opts request = "Fuel" <> PP.align (PP.vsep methodChain)+ where+ methodChain = catMaybes+ [ Just $ mconcat [".", method, "(", url, ")"]+ , body+ , Just ".responseObject(handler)"+ ]++ method =+ request ^. F.reqMethod . to (stext . T.toLower . T.decodeUtf8)++ url =+ mkUrl opts (request ^. F.reqUrl . F.path) <> mkQueryParams request++ body = fmap (\b -> mconcat+ [ ".body(Gson().toJson("+ , kotlinBodyArg+ , ", "+ , kotlinTypeRef b+ , "::class.java))"+ ]+ ) $ request ^. F.reqBody+++mkUrl :: KotlinOptions -> [F.Segment KotlinClass] -> Doc+mkUrl _opts segments = mconcat . PP.punctuate " + " $+ PP.dquotes "/" :+ PP.punctuate (" + " <> PP.dquotes "/") (map segmentToDoc segments)+ where+ segmentToDoc :: F.Segment KotlinClass -> Doc+ segmentToDoc segment =+ case F.unSegment segment of+ F.Static path -> PP.dquotes (stext (F.unPathSegment path))+ F.Cap _arg -> kotlinCaptureArg segment++-- TODO: implements+mkQueryParams :: F.Req KotlinClass -> Doc+mkQueryParams _request = ""+ -- if null (request ^. F.reqUrl . F.queryStr) then+ -- empty+ -- else+ -- " +" <+> dquotes "?" <+> "+" <+>+ -- "params.joinToString(" <> dquotes "&" <> ")"++{-|+ Options to configure how code is generated.+-}+data KotlinOptions = KotlinOptions+ { urlPrefix :: UrlPrefix+ , emptyResponseKotlinTypes :: [KotlinClass]+ -- ^ Types that represent an empty Http response.+ , stringKotlinTypes :: [KotlinClass]+ -- ^ Types that represent a String.+ }++data UrlPrefix+ = Static Text+ | Dynamic++{-|+ Default options for generating Kotlin code.+ The default options are:+ > { urlPrefix = Static ""+ > , emptyResponseKotlinTypes = [ toKotlinType NoContent ]+ > , stringKotlinTypes = [ toKotlinType "" ]+ > }+-}+defKotlinOptions :: KotlinOptions+defKotlinOptions = KotlinOptions+ { urlPrefix = Static ""+ , emptyResponseKotlinTypes =+ [ toKotlinType' NoContent+ , toKotlinType' ()+ ]+ , stringKotlinTypes =+ [ toKotlinType' ("" :: String)+ , toKotlinType' ("" :: Text)+ ]+ }++---++docToText :: Doc -> Text+docToText =+ L.toStrict . PP.displayT . PP.renderPretty 0.4 100++stext :: Text -> Doc+stext = PP.text . L.fromStrict
src/Servant/Kotlin/Type.hs view
@@ -1,185 +1,185 @@-{-# LANGUAGE DefaultSignatures #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE TypeSynonymInstances #-} - -module Servant.Kotlin.Type - ( KotlinClass (..) - , KotlinPrimitiveClass (..) - , KotlinExClass (..) - , KotlinDataClass (..) - , KotlinFields (..) - , KotlinField (..) - , KotlinType (..) - , GenericKotlinType (..) - , GenericKotlinFields (..) - , toKotlinType' - ) where - -import Data.Int (Int16, Int32, Int64, Int8) -import Data.IntMap (IntMap) -import Data.Map (Map) -import Data.Maybe (fromMaybe) -import Data.Proxy (Proxy (..)) -import Data.Text (Text, pack) -import Data.Time (UTCTime) -import GHC.Generics -import Servant.API (NoContent (..)) - -data KotlinClass - = PrimitiveClass KotlinPrimitiveClass - | DataClass KotlinDataClass - | ExClass KotlinExClass - deriving (Show, Eq) - -data KotlinPrimitiveClass - = KDouble - | KFloat - | KLong - | KInt - | KShort - | KByte - | KChar - | KBoolean - | KArray KotlinClass - | KString - | KUnit - | KNullable KotlinClass - | KAny - deriving (Show, Eq) - -data KotlinExClass - = KList KotlinClass - | KHashMap KotlinClass KotlinClass - | KPair KotlinClass KotlinClass - | KTime - deriving (Show, Eq) - -data KotlinDataClass = KotlinDataClass Text KotlinFields deriving (Show, Eq) - -data KotlinFields - = Node KotlinField - | Brunch KotlinFields KotlinFields - deriving (Show, Eq) - -data KotlinField = KotlinField Text KotlinClass deriving (Show, Eq) - -class KotlinType a where - toKotlinType :: a -> Maybe KotlinClass - toKotlinType = genericToKotlinType . from - default toKotlinType :: (Generic a, GenericKotlinType (Rep a)) => - a -> Maybe KotlinClass - -class GenericKotlinType f where - genericToKotlinType :: f a -> Maybe KotlinClass - -instance (Datatype d, GenericKotlinFields f) - => GenericKotlinType (D1 d f) where - genericToKotlinType datatype = fmap DataClass $ - KotlinDataClass (pack $ datatypeName datatype) - <$> genericToKotlinFields (unM1 datatype) - -class GenericKotlinFields f where - genericToKotlinFields :: f a -> Maybe KotlinFields - -instance (Constructor c, GenericKotlinFields f) - => GenericKotlinFields (C1 c f) where - genericToKotlinFields constructor = - if conIsRecord constructor then - genericToKotlinFields (unM1 constructor) - else - Nothing - -instance GenericKotlinFields (f :+: g) where - genericToKotlinFields _ = Nothing - -instance (Selector s, GenericKotlinType a) - => GenericKotlinFields (S1 s a) where - genericToKotlinFields selector = - case selName selector of - "" -> Nothing - name -> - Node . KotlinField (pack name) - <$> genericToKotlinType (undefined :: a p) - -instance (GenericKotlinFields f, GenericKotlinFields g) - => GenericKotlinFields (f :*: g) where - genericToKotlinFields _ = - Brunch <$> genericToKotlinFields (undefined :: f p) - <*> genericToKotlinFields (undefined :: g p) - -instance GenericKotlinFields U1 where - genericToKotlinFields _ = Nothing - -instance KotlinType a => GenericKotlinType (Rec0 a) where - genericToKotlinType _ = toKotlinType (Proxy :: Proxy a) - -instance KotlinType a => KotlinType [a] where - toKotlinType _ = ExClass . KList <$> toKotlinType (Proxy :: Proxy a) - -instance KotlinType a => KotlinType (Maybe a) where - toKotlinType _ = - PrimitiveClass . KNullable <$> toKotlinType (Proxy :: Proxy a) - -instance KotlinType () where - toKotlinType _ = Just $ PrimitiveClass KUnit - -instance KotlinType Text where - toKotlinType _ = Just $ PrimitiveClass KString - -instance KotlinType UTCTime where - toKotlinType _ = Just $ ExClass KTime - -instance KotlinType Float where - toKotlinType _ = Just $ PrimitiveClass KFloat - -instance KotlinType Double where - toKotlinType _ = Just $ PrimitiveClass KDouble - -instance KotlinType Int where - toKotlinType _ = Just $ PrimitiveClass KInt - -instance KotlinType Int8 where - toKotlinType _ = Just $ PrimitiveClass KByte - -instance KotlinType Int16 where - toKotlinType _ = Just $ PrimitiveClass KShort - -instance KotlinType Int32 where - toKotlinType _ = Just $ PrimitiveClass KInt - -instance KotlinType Int64 where - toKotlinType _ = Just $ PrimitiveClass KLong - -instance KotlinType Char where - toKotlinType _ = Just $ PrimitiveClass KChar - -instance KotlinType Bool where - toKotlinType _ = Just $ PrimitiveClass KBoolean - -instance (KotlinType a, KotlinType b) => KotlinType (a, b) where - toKotlinType _ = fmap ExClass $ - KPair - <$> toKotlinType (Proxy :: Proxy a) - <*> toKotlinType (Proxy :: Proxy b) - -instance (KotlinType a) => KotlinType (Proxy a) where - toKotlinType _ = toKotlinType (undefined :: a) - -instance (KotlinType k, KotlinType v) => KotlinType (Map k v) where - toKotlinType _ = fmap ExClass $ - KHashMap - <$> toKotlinType (Proxy :: Proxy k) - <*> toKotlinType (Proxy :: Proxy v) - -instance (KotlinType v) => KotlinType (IntMap v) where - toKotlinType _ = - PrimitiveClass . KArray <$> toKotlinType (Proxy :: Proxy v) - -instance KotlinType NoContent where - toKotlinType _ = Just $ PrimitiveClass KUnit - -toKotlinType' :: (KotlinType a) => a -> KotlinClass -toKotlinType' = fromMaybe (PrimitiveClass KAny) . toKotlinType +{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeSynonymInstances #-}++module Servant.Kotlin.Type+ ( KotlinClass (..)+ , KotlinPrimitiveClass (..)+ , KotlinExClass (..)+ , KotlinDataClass (..)+ , KotlinFields (..)+ , KotlinField (..)+ , KotlinType (..)+ , GenericKotlinType (..)+ , GenericKotlinFields (..)+ , toKotlinType'+ ) where++import Data.Int (Int16, Int32, Int64, Int8)+import Data.IntMap (IntMap)+import Data.Map (Map)+import Data.Maybe (fromMaybe)+import Data.Proxy (Proxy (..))+import Data.Text (Text, pack)+import Data.Time (UTCTime)+import GHC.Generics+import Servant.API (NoContent (..))++data KotlinClass+ = PrimitiveClass KotlinPrimitiveClass+ | DataClass KotlinDataClass+ | ExClass KotlinExClass+ deriving (Show, Eq)++data KotlinPrimitiveClass+ = KDouble+ | KFloat+ | KLong+ | KInt+ | KShort+ | KByte+ | KChar+ | KBoolean+ | KArray KotlinClass+ | KString+ | KUnit+ | KNullable KotlinClass+ | KAny+ deriving (Show, Eq)++data KotlinExClass+ = KList KotlinClass+ | KHashMap KotlinClass KotlinClass+ | KPair KotlinClass KotlinClass+ | KTime+ deriving (Show, Eq)++data KotlinDataClass = KotlinDataClass Text KotlinFields deriving (Show, Eq)++data KotlinFields+ = Node KotlinField+ | Brunch KotlinFields KotlinFields+ deriving (Show, Eq)++data KotlinField = KotlinField Text KotlinClass deriving (Show, Eq)++class KotlinType a where+ toKotlinType :: a -> Maybe KotlinClass+ toKotlinType = genericToKotlinType . from+ default toKotlinType :: (Generic a, GenericKotlinType (Rep a)) =>+ a -> Maybe KotlinClass++class GenericKotlinType f where+ genericToKotlinType :: f a -> Maybe KotlinClass++instance (Datatype d, GenericKotlinFields f)+ => GenericKotlinType (D1 d f) where+ genericToKotlinType datatype = fmap DataClass $+ KotlinDataClass (pack $ datatypeName datatype)+ <$> genericToKotlinFields (unM1 datatype)++class GenericKotlinFields f where+ genericToKotlinFields :: f a -> Maybe KotlinFields++instance (Constructor c, GenericKotlinFields f)+ => GenericKotlinFields (C1 c f) where+ genericToKotlinFields constructor =+ if conIsRecord constructor then+ genericToKotlinFields (unM1 constructor)+ else+ Nothing++instance GenericKotlinFields (f :+: g) where+ genericToKotlinFields _ = Nothing++instance (Selector s, GenericKotlinType a)+ => GenericKotlinFields (S1 s a) where+ genericToKotlinFields selector =+ case selName selector of+ "" -> Nothing+ name ->+ Node . KotlinField (pack name)+ <$> genericToKotlinType (undefined :: a p)++instance (GenericKotlinFields f, GenericKotlinFields g)+ => GenericKotlinFields (f :*: g) where+ genericToKotlinFields _ =+ Brunch <$> genericToKotlinFields (undefined :: f p)+ <*> genericToKotlinFields (undefined :: g p)++instance GenericKotlinFields U1 where+ genericToKotlinFields _ = Nothing++instance KotlinType a => GenericKotlinType (Rec0 a) where+ genericToKotlinType _ = toKotlinType (Proxy :: Proxy a)++instance KotlinType a => KotlinType [a] where+ toKotlinType _ = ExClass . KList <$> toKotlinType (Proxy :: Proxy a)++instance KotlinType a => KotlinType (Maybe a) where+ toKotlinType _ =+ PrimitiveClass . KNullable <$> toKotlinType (Proxy :: Proxy a)++instance KotlinType () where+ toKotlinType _ = Just $ PrimitiveClass KUnit++instance KotlinType Text where+ toKotlinType _ = Just $ PrimitiveClass KString++instance KotlinType UTCTime where+ toKotlinType _ = Just $ ExClass KTime++instance KotlinType Float where+ toKotlinType _ = Just $ PrimitiveClass KFloat++instance KotlinType Double where+ toKotlinType _ = Just $ PrimitiveClass KDouble++instance KotlinType Int where+ toKotlinType _ = Just $ PrimitiveClass KInt++instance KotlinType Int8 where+ toKotlinType _ = Just $ PrimitiveClass KByte++instance KotlinType Int16 where+ toKotlinType _ = Just $ PrimitiveClass KShort++instance KotlinType Int32 where+ toKotlinType _ = Just $ PrimitiveClass KInt++instance KotlinType Int64 where+ toKotlinType _ = Just $ PrimitiveClass KLong++instance KotlinType Char where+ toKotlinType _ = Just $ PrimitiveClass KChar++instance KotlinType Bool where+ toKotlinType _ = Just $ PrimitiveClass KBoolean++instance (KotlinType a, KotlinType b) => KotlinType (a, b) where+ toKotlinType _ = fmap ExClass $+ KPair+ <$> toKotlinType (Proxy :: Proxy a)+ <*> toKotlinType (Proxy :: Proxy b)++instance (KotlinType a) => KotlinType (Proxy a) where+ toKotlinType _ = toKotlinType (undefined :: a)++instance (KotlinType k, KotlinType v) => KotlinType (Map k v) where+ toKotlinType _ = fmap ExClass $+ KHashMap+ <$> toKotlinType (Proxy :: Proxy k)+ <*> toKotlinType (Proxy :: Proxy v)++instance (KotlinType v) => KotlinType (IntMap v) where+ toKotlinType _ =+ PrimitiveClass . KArray <$> toKotlinType (Proxy :: Proxy v)++instance KotlinType NoContent where+ toKotlinType _ = Just $ PrimitiveClass KUnit++toKotlinType' :: (KotlinType a) => a -> KotlinClass+toKotlinType' = fromMaybe (PrimitiveClass KAny) . toKotlinType
test/Servant/Kotlin/Internal/GenerateSpec.hs view
@@ -1,140 +1,140 @@-{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeOperators #-} - -module Servant.Kotlin.Internal.GenerateSpec - ( main - , spec - , Todo (..) - ) where - -import Data.Proxy (Proxy (Proxy)) -import Data.Text (Text) -import qualified Data.Text as T -import GHC.Generics (Generic) -import Servant.Kotlin.Internal.Generate -import Servant.Kotlin.Type -import Test.Hspec (Spec, describe, hspec, it, - shouldBe) -import Test.TestAPI - -data Todo = Todo - { todoId :: Int - , title :: Text - , done :: Bool - } deriving (Generic, Show, Eq, KotlinType) - -data Hoge - = Hoge1 Int - | Hoge2 Bool - deriving (Generic, Show, Eq, KotlinType) - -main :: IO () -main = hspec spec - -spec :: Spec -spec = do - describe "generateKotlinForDefDataClass :: Proxy a -> [Text]" $ do - it "sample Todo type" $ - generateKotlinForDefDataClass (Proxy :: Proxy Todo) `shouldBe` - [ "data class Todo(val todoId: Int, val title: String, val done: Boolean)" ] - it "not data class, e.g. Int" $ - generateKotlinForDefDataClass (Proxy :: Proxy Int) `shouldBe` [] - it "return Nothing e.g. Product Type" $ - generateKotlinForDefDataClass (Proxy :: Proxy Hoge) `shouldBe` [] - - describe "generateKotlinForAPIClass :: Text -> [Text] -> [Text]" $ do - it "body is empty" $ - generateKotlinForAPIClass "TodoAPI" [] `shouldBe` - [ "class TodoAPI(private val baseURL: String) {" - , T.intercalate "\n" - [ " init {" - , " FuelManager.instance.apply {" - , " basePath = baseURL" - , " baseHeaders = mapOf(\"Content-Type\" to \"application/json\", \"Device\" to \"Android\")" - , " }" - , " }" - ] - , "}" - ] - it "body is non empty" $ - generateKotlinForAPIClass "TodoAPI" ["aaa", "bbb\nccc"] `shouldBe` - [ "class TodoAPI(private val baseURL: String) {" - , T.intercalate "\n" - [ " init {" - , " FuelManager.instance.apply {" - , " basePath = baseURL" - , " baseHeaders = mapOf(\"Content-Type\" to \"application/json\", \"Device\" to \"Android\")" - , " }" - , " }" - ] - , " aaa" - , T.intercalate "\n" - [ " bbb" - , " ccc" - ] - , "}" - ] - - describe "generateKotlinForAPI :: Proxy api -> [Text]" $ - it "sample CRUD" $ - generateKotlinForAPI (Proxy :: Proxy TestApi) `shouldBe` - [ T.intercalate "\n" - [ "fun getOne(handler: (Request, Response, Result<Int, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"one\")" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun postTwo(body: String, handler: (Request, Response, Result<Int?, FuelError>) -> Unit) {" - , " Fuel.post(\"/\" + \"two\")" - , " .body(Gson().toJson(body, String::class.java))" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun getBooksById(capture_id: Int, handler: (Request, Response, Result<Book, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"books\" + \"/\" + capture_id)" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun getBooksByTitle(capture_title: String, handler: (Request, Response, Result<Book, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"books\" + \"/\" + capture_title)" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun getBooks(query_published: Boolean, query_sort: String?, query_year: Int?, query_filters: List<Boolean?>, handler: (Request, Response, Result<List<Book>, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"books\")" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun postBooks(body: Book, handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {" - , " Fuel.post(\"/\" + \"books\")" - , " .body(Gson().toJson(body, Book::class.java))" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun getNothing(handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"nothing\")" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun putNothing(handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {" - , " Fuel.put(\"/\" + \"nothing\")" - , " .responseObject(handler)" - , "}" - ] - , T.intercalate "\n" - [ "fun getWithaheader(header_myTextHeader: String?, header_MyIntHeader: Int?, handler: (Request, Response, Result<String, FuelError>) -> Unit) {" - , " Fuel.get(\"/\" + \"with-a-header\")" - , " .responseObject(handler)" - , "}" - ] - ] +{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}++module Servant.Kotlin.Internal.GenerateSpec+ ( main+ , spec+ , Todo (..)+ ) where++import Data.Proxy (Proxy (Proxy))+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic)+import Servant.Kotlin.Internal.Generate+import Servant.Kotlin.Type+import Test.Hspec (Spec, describe, hspec, it,+ shouldBe)+import Test.TestAPI++data Todo = Todo+ { todoId :: Int+ , title :: Text+ , done :: Bool+ } deriving (Generic, Show, Eq, KotlinType)++data Hoge+ = Hoge1 Int+ | Hoge2 Bool+ deriving (Generic, Show, Eq, KotlinType)++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "generateKotlinForDefDataClass :: Proxy a -> [Text]" $ do+ it "sample Todo type" $+ generateKotlinForDefDataClass (Proxy :: Proxy Todo) `shouldBe`+ [ "data class Todo(val todoId: Int, val title: String, val done: Boolean)" ]+ it "not data class, e.g. Int" $+ generateKotlinForDefDataClass (Proxy :: Proxy Int) `shouldBe` []+ it "return Nothing e.g. Product Type" $+ generateKotlinForDefDataClass (Proxy :: Proxy Hoge) `shouldBe` []++ describe "generateKotlinForAPIClass :: Text -> [Text] -> [Text]" $ do+ it "body is empty" $+ generateKotlinForAPIClass "TodoAPI" [] `shouldBe`+ [ "class TodoAPI(private val baseURL: String) {"+ , T.intercalate "\n"+ [ " init {"+ , " FuelManager.instance.apply {"+ , " basePath = baseURL"+ , " baseHeaders = mapOf(\"Content-Type\" to \"application/json\", \"Device\" to \"Android\")"+ , " }"+ , " }"+ ]+ , "}"+ ]+ it "body is non empty" $+ generateKotlinForAPIClass "TodoAPI" ["aaa", "bbb\nccc"] `shouldBe`+ [ "class TodoAPI(private val baseURL: String) {"+ , T.intercalate "\n"+ [ " init {"+ , " FuelManager.instance.apply {"+ , " basePath = baseURL"+ , " baseHeaders = mapOf(\"Content-Type\" to \"application/json\", \"Device\" to \"Android\")"+ , " }"+ , " }"+ ]+ , " aaa"+ , T.intercalate "\n"+ [ " bbb"+ , " ccc"+ ]+ , "}"+ ]++ describe "generateKotlinForAPI :: Proxy api -> [Text]" $+ it "sample CRUD" $+ generateKotlinForAPI (Proxy :: Proxy TestApi) `shouldBe`+ [ T.intercalate "\n"+ [ "fun getOne(handler: (Request, Response, Result<Int, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"one\")"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun postTwo(body: String, handler: (Request, Response, Result<Int?, FuelError>) -> Unit) {"+ , " Fuel.post(\"/\" + \"two\")"+ , " .body(Gson().toJson(body, String::class.java))"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun getBooksById(capture_id: Int, handler: (Request, Response, Result<Book, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"books\" + \"/\" + capture_id)"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun getBooksByTitle(capture_title: String, handler: (Request, Response, Result<Book, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"books\" + \"/\" + capture_title)"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun getBooks(query_published: Boolean, query_sort: String?, query_year: Int?, query_filters: List<Boolean?>, handler: (Request, Response, Result<List<Book>, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"books\")"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun postBooks(body: Book, handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {"+ , " Fuel.post(\"/\" + \"books\")"+ , " .body(Gson().toJson(body, Book::class.java))"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun getNothing(handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"nothing\")"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun putNothing(handler: (Request, Response, Result<Unit, FuelError>) -> Unit) {"+ , " Fuel.put(\"/\" + \"nothing\")"+ , " .responseObject(handler)"+ , "}"+ ]+ , T.intercalate "\n"+ [ "fun getWithaheader(header_myTextHeader: String?, header_MyIntHeader: Int?, handler: (Request, Response, Result<String, FuelError>) -> Unit) {"+ , " Fuel.get(\"/\" + \"with-a-header\")"+ , " .responseObject(handler)"+ , "}"+ ]+ ]
test/Spec.hs view
@@ -1,1 +1,1 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-} +{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
test/Test/TestAPI.hs view
@@ -1,55 +1,55 @@-{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE TypeOperators #-} - -module Test.TestAPI where - -import Data.Aeson (ToJSON) -import Data.Proxy (Proxy (Proxy)) -import Data.Text (Text) -import GHC.Generics (Generic) -import Servant.API ((:<|>), (:>), Capture, Get, GetNoContent, - Header, JSON, NoContent, Post, - PostNoContent, Put, QueryFlag, QueryParam, - QueryParams, ReqBody) -import Servant.Kotlin.Type (KotlinType) - -newtype Book = Book - { title :: Text - } deriving (Generic, KotlinType, Show, Eq) - -instance ToJSON Book - -type TestApi = - "one" - :> Get '[JSON] Int - :<|> "two" - :> ReqBody '[JSON] Text - :> Post '[JSON] (Maybe Int) - :<|> "books" - :> Capture "id" Int - :> Get '[JSON] Book - :<|> "books" - :> Capture "title" Text - :> Get '[JSON] Book - :<|> "books" - :> QueryFlag "published" - :> QueryParam "sort" Text - :> QueryParam "year" Int - :> QueryParams "filters" (Maybe Bool) - :> Get '[JSON] [Book] - :<|> "books" - :> ReqBody '[JSON] Book - :> PostNoContent '[JSON] NoContent - :<|> "nothing" - :> GetNoContent '[JSON] NoContent - :<|> "nothing" - :> Put '[JSON] () -- old way to specify no content - :<|> "with-a-header" - :> Header "myTextHeader" Text - :> Header "MyIntHeader" Int - :> Get '[JSON] Text - -testApi :: Proxy TestApi -testApi = Proxy +{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}++module Test.TestAPI where++import Data.Aeson (ToJSON)+import Data.Proxy (Proxy (Proxy))+import Data.Text (Text)+import GHC.Generics (Generic)+import Servant.API ((:<|>), (:>), Capture, Get, GetNoContent,+ Header, JSON, NoContent, Post,+ PostNoContent, Put, QueryFlag, QueryParam,+ QueryParams, ReqBody)+import Servant.Kotlin.Type (KotlinType)++newtype Book = Book+ { title :: Text+ } deriving (Generic, KotlinType, Show, Eq)++instance ToJSON Book++type TestApi =+ "one"+ :> Get '[JSON] Int+ :<|> "two"+ :> ReqBody '[JSON] Text+ :> Post '[JSON] (Maybe Int)+ :<|> "books"+ :> Capture "id" Int+ :> Get '[JSON] Book+ :<|> "books"+ :> Capture "title" Text+ :> Get '[JSON] Book+ :<|> "books"+ :> QueryFlag "published"+ :> QueryParam "sort" Text+ :> QueryParam "year" Int+ :> QueryParams "filters" (Maybe Bool)+ :> Get '[JSON] [Book]+ :<|> "books"+ :> ReqBody '[JSON] Book+ :> PostNoContent '[JSON] NoContent+ :<|> "nothing"+ :> GetNoContent '[JSON] NoContent+ :<|> "nothing"+ :> Put '[JSON] () -- old way to specify no content+ :<|> "with-a-header"+ :> Header "myTextHeader" Text+ :> Header "MyIntHeader" Int+ :> Get '[JSON] Text++testApi :: Proxy TestApi+testApi = Proxy