@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE RecordWildCards #-}
23{-# LANGUAGE FlexibleContexts #-}
34{-# LANGUAGE MultiParamTypeClasses #-}
@@ -19,8 +20,11 @@ import Elm.Json (jsonParserForType, jsonSerForType)
1920import qualified Elm.Module as Elm
2021import Elm.TyRep (ETCon(..), EType(..), ETypeDef(..), toElmType)
2122import Elm.TyRender (renderElm)
23+#if MIN_VERSION_elm_bridge(0,6,0)
24+import Elm.Versions (ElmVersion(Elm0p19))
25+#else
2226import Elm.Versions (ElmVersion(Elm0p18))
23−
27+#endif
2428import Servant.Elm.Internal.Foreign (LangElm, getEndpoints)
2529import qualified Servant.Foreign as F
2630import System.Directory (createDirectoryIfMissing)
@@ -49,14 +53,22 @@ data ElmOptions = ElmOptions
4953 argument.
5054 -}
5155 urlPrefix :: UrlPrefix
52− , elmTypeAlterations :: (EType -> EType)
56+ , elmTypeAlterations :: (EType -> EType)
5357 -- ^ Alterations to perform on ETypes before code generation.
5458 , elmAlterations :: (ETypeDef -> ETypeDef)
5559 -- ^ Alterations to perform on ETypeDefs before code generation.
60+ , elmToString :: (EType -> Text)
61+ -- ^ Elm functions creating a string from a given type.
5662 , emptyResponseElmTypes :: [EType]
5763 -- ^ Types that represent an empty Http response.
5864 , stringElmTypes :: [EType]
5965 -- ^ Types that represent a String.
66+ , expectJsonMethod :: Text
67+ -- ^ The function to use for JSON responses.
68+ , expectStringMethod :: Text
69+ -- ^ The function to use for string responses.
70+ , httpErrorType :: Text
71+ -- ^ The type to use for Http errors.
6072 }
6173
6274
@@ -80,6 +92,9 @@ The default options are:
8092> , stringElmTypes =
8193> [ getType (Proxy :: Proxy String)
8294> , getType (Proxy :: Proxy T.Text) ]
95+> , expectJsonMethod = "Http.expectJson"
96+> , expectStringMethod = "Http.expectString"
97+> , httpErrorType = "Http.Error"
8398> }
8499-}
85100defElmOptions :: ElmOptions
@@ -87,6 +102,7 @@ defElmOptions = ElmOptions
87102 { urlPrefix = Static ""
88103 , elmTypeAlterations = Elm.defaultTypeAlterations
89104 , elmAlterations = Elm.defaultAlterations
105+ , elmToString = defaultElmToString
90106 , emptyResponseElmTypes =
91107 [ toElmType (Proxy :: Proxy ())
92108 ]
@@ -94,6 +110,9 @@ defElmOptions = ElmOptions
94110 [ toElmType (Proxy :: Proxy String)
95111 , toElmType (Proxy :: Proxy T.Text)
96112 ]
113+ , expectJsonMethod = "Http.expectJson"
114+ , expectStringMethod = "Http.expectString"
115+ , httpErrorType = "Http.Error"
97116 }
98117
99118
@@ -112,6 +131,7 @@ The default required imports are:
112131> import Set
113132> import Http
114133> import String
134+> import Url
115135> import Url.Builder
116136-}
117137defElmImports :: Text
@@ -125,14 +145,8 @@ defElmImports =
125145 , "import Set"
126146 , "import Http"
127147 , "import String"
148+ , "import Url"
128149 , "import Url.Builder"
129− , ""
130− , "maybeBoolToIntStr : Maybe Bool -> String"
131− , "maybeBoolToIntStr mx ="
132− , " case mx of"
133− , " Nothing -> \"\""
134− , " Just True -> \"1\""
135− , " Just False -> \"0\""
136150 ]
137151
138152{-|
@@ -153,7 +167,12 @@ generateElmModuleWith ::
153167generateElmModuleWith options namespace imports rootDir typeDefs api = do
154168 let out =
155169 T.unlines $
156− [ T.pack $ Elm.moduleHeader Elm0p18 moduleName
170+ [
171+#if MIN_VERSION_elm_bridge(0,6,0)
172+ T.pack $ Elm.moduleHeader Elm0p19 moduleName
173+#else
174+ T.pack $ Elm.moduleHeader Elm0p18 moduleName
175+#endif
157176 , ""
158177 , imports
159178 , T.pack $ Elm.makeModuleContentWithAlterations (elmAlterations options) typeDefs
@@ -240,7 +259,7 @@ generateElmForRequest opts request =
240259 fnName =
241260 request ^. F.reqFuncName . to (replace . F.camelCase) . to stext
242261
243− replace = T.replace "-" "" . T.replace "." ""
262+ replace = T.replace "-" "" . T.replace "." "" . T.replace " " ""
244263
245264 typeSignature =
246265 mkTypeSignature opts request
@@ -302,7 +321,7 @@ mkTypeSignature opts request =
302321 toMsgType :: Maybe Doc
303322 toMsgType = do
304323 result <- fmap elmTypeRef $ request ^. F.reqReturnType
305− Just ("(Result Http.Error " <+> parens result <+> " -> msg)")
324+ Just ("(Result" <+> stext (httpErrorType opts) <+> parens result <+> "-> msg)")
306325
307326 returnType :: Maybe Doc
308327 returnType = do
@@ -320,7 +339,7 @@ elmCaptureArg segment =
320339 "capture_" <>
321340 F.captureArg segment ^. F.argName . to (stext . replace . F.unPathSegment)
322341 where
323− replace = T.replace "-" "_"
342+ replace = T.replace "-" "_" . T.replace " " "_"
324343
325344
326345elmQueryArg :: F.QueryArg EType -> Doc
@@ -392,15 +411,11 @@ mkLetParams opts request =
392411 let
393412 argType = qarg ^. F.queryArgName . F.argType
394413 wrapped = isElmMaybeType argType
395− -- Don't use "toString" on Elm Strings, otherwise we get extraneous quotes.
396414 toStringSrc =
397− if isElmStringType opts argType || isElmMaybeStringType opts argType then
398− ""
399− else
400− "String.fromInt >> "
415+ toString opts (maybeOf argType)
401416 in
402417 "[" <+> (if wrapped then elmName else "Just" <+> elmName) <> line <>
403− (indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Url.Builder.string" <+> dquotes name)))
418+ (indent 4 ("|> Maybe.map" <+> composeRight [toStringSrc, "Url.Builder.string" <+> dquotes name]))
404419 <+> "]"
405420 -- (if wrapped then name else "Just" <+> name) <$>
406421 -- indent 4 ("|> Maybe.map" <+> parens (toStringSrc <> "Http.encodeUri >> (++)" <+> dquotes (elmName <> equals)) <$>
@@ -417,18 +432,18 @@ mkLetParams opts request =
417432 F.List ->
418433 let
419434 argType = qarg ^. F.queryArgName . F.argType
420− convertedVal =
421− if isElmListOfMaybeBoolType argType then
422− parens ("maybeBoolToIntStr" <+> "val")
423− else
424− "val"
435+ toStringSrc =
436+ toString opts (listOf (maybeOf argType))
425437 in
426438 elmName <$>
427439 indent 4 ("|> List.map"
428− <+> parens (backslash <> "val ->" <+> "Just"
429− <+> parens ("Url.Builder.string"
430− <+> dquotes (name <> "[]")
431− <+> convertedVal)))
440+ <+> composeRight
441+ [ toStringSrc
442+ , "Url.Builder.string" <+> dquotes (name <> "[]")
443+ , "Just"
444+ ]
445+ )
446+
432447 where
433448 elmName = elmQueryArg qarg
434449 name = qarg ^. F.queryArgName . F.argName . to (stext . F.unPathSegment)
@@ -464,13 +479,9 @@ mkRequest opts request =
464479 headerArgName = elmHeaderArg header
465480 argType = header ^. F.headerArg . F.argType
466481 wrapped = isElmMaybeType argType
467− toStringSrc =
468− if isElmMaybeStringType opts argType || isElmStringType opts argType then
469− mempty
470− else
471− " << String.fromInt"
482+ toStringSrc = toString opts (maybeOf argType)
472483 in
473− "Maybe.map" <+> parens (("Http.header" <+> dquotes headerName <> toStringSrc))
484+ "Maybe.map" <+> composeLeft ["Http.header" <+> dquotes headerName, toStringSrc]
474485 <+>
475486 (if wrapped then headerArgName else parens ("Just" <+> headerArgName))
476487
@@ -499,14 +510,14 @@ mkRequest opts request =
499510 expect =
500511 case request ^. F.reqReturnType of
501512 Just elmTypeExpr
502− | isEmptyType opts elmTypeExpr
513+ | isEmptyType opts $ (elmTypeAlterations opts) elmTypeExpr
503514 -- let elmConstructor = T.pack (renderElm elmTypeExpr)
504515 ->
505− "Http.expectString " <> line <+> indent i "(\\x -> case x of" <> line <+>
516+ stext (expectStringMethod opts) <> line <+> indent i "(\\x -> case x of" <> line <+>
506517 indent i "Err e -> toMsg (Err e)" <> line <+>
507518 indent i "Ok _ -> toMsg (Ok ()))"
508519 Just elmTypeExpr ->
509− "Http.expectJson toMsg" <+> renderDecoderName elmTypeExpr
520+ stext (expectJsonMethod opts) <+> "toMsg" <+> renderDecoderName ((elmTypeAlterations opts) elmTypeExpr)
510521 Nothing -> error "mkHttpRequest: no reqReturnType?"
511522 -- case request ^. F.reqReturnType of
512523 -- Just elmTypeExpr | isEmptyType opts elmTypeExpr ->
@@ -534,6 +545,8 @@ renderDecoderName elmTypeExpr =
534545 parens ("Json.Decode.list " <> parens (renderDecoderName t))
535546 ETyApp (ETyCon (ETCon "Maybe")) t ->
536547 parens ("Json.Decode.maybe " <> parens (renderDecoderName t))
548+ ETyApp x y ->
549+ parens (renderDecoderName x <+> renderDecoderName y)
537550 ETyCon (ETCon "Int") -> "Json.Decode.int"
538551 ETyCon (ETCon "String") -> "Json.Decode.string"
539552 _ -> ("jsonDec" <> stext (T.pack (renderElm elmTypeExpr)))
@@ -551,7 +564,7 @@ mkUrl opts segments =
551564 where
552565 urlBuilder :: Doc
553566 urlBuilder = case urlPrefix opts of
554− Dynamic -> "Url.Builder.absolute" :: Doc
567+ Dynamic -> "Url.Builder.crossOrigin urlBase" :: Doc
555568 Static url -> "Url.Builder.crossOrigin" <+> dquotes (stext url)
556569
557570 segmentToDoc :: F.Segment EType -> Doc
@@ -561,14 +574,10 @@ mkUrl opts segments =
561574 dquotes (stext (F.unPathSegment path))
562575 F.Cap arg ->
563576 let
564− -- Don't use "toString" on Elm Strings, otherwise we get extraneous quotes.
565577 toStringSrc =
566− if isElmStringType opts (arg ^. F.argType) then
567− empty
568− else
569− " |> String.fromInt"
578+ toString opts (elmTypeAlterations opts (maybeOf (arg ^. F.argType)))
570579 in
571− (elmCaptureArg s) <> toStringSrc
580+ "Url.percentEncode " <> pipeRight [elmCaptureArg s, toStringSrc]
572581
573582
574583mkQueryParams
@@ -586,7 +595,7 @@ response body.
586595-}
587596isEmptyType :: ElmOptions -> EType -> Bool
588597isEmptyType opts elmTypeExpr =
589− elmTypeExpr `elem` emptyResponseElmTypes opts
598+ (elmTypeAlterations opts elmTypeExpr) `elem` emptyResponseElmTypes opts
590599
591600
592601{- | Determines whether we call `toString` on URL captures and query params of
@@ -594,7 +603,7 @@ this type in Elm.
594603-}
595604isElmStringType :: ElmOptions -> EType -> Bool
596605isElmStringType opts elmTypeExpr =
597− elmTypeExpr `elem` stringElmTypes opts
606+ (elmTypeAlterations opts elmTypeExpr) `elem` stringElmTypes opts
598607
599608{- | Determines whether a type is 'Maybe a' where 'a' is something akin to a 'String'.
600609-}
@@ -632,3 +641,44 @@ elmList ds = lbracket <+> hsep (punctuate (line <> comma) ds) <$> rbracket
632641elmListOfMaybes :: [Doc] -> Doc
633642elmListOfMaybes [] = lbracket <> rbracket
634643elmListOfMaybes ds = "List.filterMap identity" <$> indent 4 (elmList ds)
644+
645+defaultElmToString :: EType -> Text
646+defaultElmToString argType =
647+ case argType of
648+ ETyCon (ETCon "Bool") -> "(\\value -> if value then \"true\" else \"false\")"
649+ ETyCon (ETCon "Float") -> "String.fromFloat"
650+ ETyCon (ETCon "Char") -> "String.fromChar"
651+ ETyApp (ETyCon (ETCon "Maybe")) v -> "(Maybe.map " <> defaultElmToString v <> " >> Maybe.withDefault \"\")"
652+ _ -> "String.fromInt"
653+
654+
655+maybeOf :: EType -> EType
656+maybeOf (ETyApp (ETyCon (ETCon "Maybe")) v) = v
657+maybeOf v = v
658+
659+listOf :: EType -> EType
660+listOf (ETyApp (ETyCon (ETCon "List")) v) = v
661+listOf v = v
662+
663+toString :: ElmOptions -> EType -> Doc
664+toString opts argType =
665+ if isElmStringType opts argType then
666+ mempty
667+ else
668+ stext $ elmToString opts argType
669+
670+pipeLeft :: [Doc] -> Doc
671+pipeLeft =
672+ encloseSep lparen rparen " <| " . filter (not . isEmpty)
673+
674+pipeRight :: [Doc] -> Doc
675+pipeRight =
676+ encloseSep lparen rparen " |> " . filter (not . isEmpty)
677+
678+composeLeft :: [Doc] -> Doc
679+composeLeft =
680+ encloseSep lparen rparen " << " . filter (not . isEmpty)
681+
682+composeRight :: [Doc] -> Doc
683+composeRight =
684+ encloseSep lparen rparen " >> " . filter (not . isEmpty)