{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Functionality to Generate Haskell Code out of an OpenAPI definition File
module OpenAPI.Generate.Main where

import Control.Monad
import qualified Data.Bifunctor as BF
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.Text as T
import Language.Haskell.TH
import Language.Haskell.TH.PprLib hiding ((<>))
import qualified OpenAPI.Common as OC
import qualified OpenAPI.Generate.Doc as Doc
import OpenAPI.Generate.Internal.Unknown
import OpenAPI.Generate.Internal.Util
import qualified OpenAPI.Generate.Model as Model
import qualified OpenAPI.Generate.ModelDependencies as Dep
import qualified OpenAPI.Generate.Monad as OAM
import qualified OpenAPI.Generate.Operation as Operation
import qualified OpenAPI.Generate.OptParse as OAO
import qualified OpenAPI.Generate.SecurityScheme as SecurityScheme
import qualified OpenAPI.Generate.Types as OAT

-- | Defines all the operations as functions and the common imports
defineOperations :: String -> OAT.OpenApiSpecification -> OAM.Generator (Q [Dep.ModuleDefinition], Dep.Models)
defineOperations :: String
-> OpenApiSpecification -> Generator (Q [ModuleDefinition], Models)
defineOperations String
moduleName OpenApiSpecification
specification =
  let paths :: [(Text, PathItemObject)]
paths = Map Text PathItemObject -> [(Text, PathItemObject)]
forall k a. Map k a -> [(k, a)]
Map.toList (Map Text PathItemObject -> [(Text, PathItemObject)])
-> Map Text PathItemObject -> [(Text, PathItemObject)]
forall a b. (a -> b) -> a -> b
$ OpenApiSpecification -> Map Text PathItemObject
OAT.openApiSpecificationPaths OpenApiSpecification
specification
   in Text
-> Generator (Q [ModuleDefinition], Models)
-> Generator (Q [ModuleDefinition], Models)
forall a. Text -> Generator a -> Generator a
OAM.nested Text
"paths" (Generator (Q [ModuleDefinition], Models)
 -> Generator (Q [ModuleDefinition], Models))
-> Generator (Q [ModuleDefinition], Models)
-> Generator (Q [ModuleDefinition], Models)
forall a b. (a -> b) -> a -> b
$ do
        [(Text, PathItemObject)] -> Generator ()
warnAboutUnknownOperations [(Text, PathItemObject)]
paths
        (([Q [ModuleDefinition]], [Models])
 -> (Q [ModuleDefinition], Models))
-> Generator ([Q [ModuleDefinition]], [Models])
-> Generator (Q [ModuleDefinition], Models)
forall a b. (a -> b) -> Generator a -> Generator b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap
          ( ([Q [ModuleDefinition]] -> Q [ModuleDefinition])
-> ([Models] -> Models)
-> ([Q [ModuleDefinition]], [Models])
-> (Q [ModuleDefinition], Models)
forall a b c d. (a -> b) -> (c -> d) -> (a, c) -> (b, d)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
BF.bimap
              ( ([[ModuleDefinition]] -> [ModuleDefinition])
-> Q [[ModuleDefinition]] -> Q [ModuleDefinition]
forall a b. (a -> b) -> Q a -> Q b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [[ModuleDefinition]] -> [ModuleDefinition]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
                  (Q [[ModuleDefinition]] -> Q [ModuleDefinition])
-> ([Q [ModuleDefinition]] -> Q [[ModuleDefinition]])
-> [Q [ModuleDefinition]]
-> Q [ModuleDefinition]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Q [ModuleDefinition]] -> Q [[ModuleDefinition]]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
forall (m :: * -> *) a. Monad m => [m a] -> m [a]
sequence
              )
              [Models] -> Models
forall (f :: * -> *) a. (Foldable f, Ord a) => f (Set a) -> Set a
Set.unions
          )
          (Generator ([Q [ModuleDefinition]], [Models])
 -> Generator (Q [ModuleDefinition], Models))
-> ([(Text, PathItemObject)]
    -> Generator ([Q [ModuleDefinition]], [Models]))
-> [(Text, PathItemObject)]
-> Generator (Q [ModuleDefinition], Models)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, PathItemObject)
 -> Generator (Q [ModuleDefinition], Models))
-> [(Text, PathItemObject)]
-> Generator ([Q [ModuleDefinition]], [Models])
forall (m :: * -> *) a b c.
Applicative m =>
(a -> m (b, c)) -> [a] -> m ([b], [c])
mapAndUnzipM ((Text
 -> PathItemObject -> Generator (Q [ModuleDefinition], Models))
-> (Text, PathItemObject)
-> Generator (Q [ModuleDefinition], Models)
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ((Text
  -> PathItemObject -> Generator (Q [ModuleDefinition], Models))
 -> (Text, PathItemObject)
 -> Generator (Q [ModuleDefinition], Models))
-> (Text
    -> PathItemObject -> Generator (Q [ModuleDefinition], Models))
-> (Text, PathItemObject)
-> Generator (Q [ModuleDefinition], Models)
forall a b. (a -> b) -> a -> b
$ String
-> Text
-> PathItemObject
-> Generator (Q [ModuleDefinition], Models)
Operation.defineOperationsForPath String
moduleName)
          ([(Text, PathItemObject)]
 -> Generator (Q [ModuleDefinition], Models))
-> [(Text, PathItemObject)]
-> Generator (Q [ModuleDefinition], Models)
forall a b. (a -> b) -> a -> b
$ [(Text, PathItemObject)]
paths

-- | Defines the @defaultURL@ and the @defaultConfiguration@ containing this URL.
defineConfigurationInformation :: String -> OAT.OpenApiSpecification -> Q Doc
defineConfigurationInformation :: String -> OpenApiSpecification -> Q Doc
defineConfigurationInformation String
moduleName OpenApiSpecification
spec =
  let defaultURL :: Text
defaultURL = [ServerObject] -> Text
getServerURL ([ServerObject] -> Text) -> [ServerObject] -> Text
forall a b. (a -> b) -> a -> b
$ OpenApiSpecification -> [ServerObject]
OAT.openApiSpecificationServers OpenApiSpecification
spec
      defaultURLName :: Name
defaultURLName = String -> Name
mkName String
"defaultURL"
      getServerURL :: [ServerObject] -> Text
getServerURL = Text -> (ServerObject -> Text) -> Maybe ServerObject -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"/" ServerObject -> Text
OAT.serverObjectUrl (Maybe ServerObject -> Text)
-> ([ServerObject] -> Maybe ServerObject) -> [ServerObject] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ServerObject] -> Maybe ServerObject
forall a. [a] -> Maybe a
Maybe.listToMaybe
      defaultApplicationNameVarName :: Name
defaultApplicationNameVarName = String -> Name
mkName String
"defaultApplicationName"
      defaultApplicationName :: Text
defaultApplicationName = InfoObject -> Text
OAT.infoObjectTitle (InfoObject -> Text) -> InfoObject -> Text
forall a b. (a -> b) -> a -> b
$ OpenApiSpecification -> InfoObject
OAT.openApiSpecificationInfo OpenApiSpecification
spec
   in String -> Doc -> Doc
Doc.addConfigurationModuleHeader String
moduleName
        (Doc -> Doc) -> ([Doc] -> Doc) -> [Doc] -> Doc
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Doc] -> Doc
vcat
        ([Doc] -> Doc) -> Q [Doc] -> Q Doc
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Q Doc] -> Q [Doc]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
forall (m :: * -> *) a. Monad m => [m a] -> m [a]
sequence
          [ Doc -> Q Doc
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Doc -> Q Doc) -> Doc -> Q Doc
forall a b. (a -> b) -> a -> b
$
              [Text] -> Doc
Doc.generateHaddockComment
                [ Text
"The default url specified by the OpenAPI specification",
                  Text
"",
                  Text
"@" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
defaultURL Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"@"
                ],
            Decs -> Doc
forall a. Ppr a => a -> Doc
ppr
              (Decs -> Doc) -> Q Decs -> Q Doc
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [d|$(Name -> Q Pat
forall (m :: * -> *). Quote m => Name -> m Pat
varP Name
defaultURLName) = T.pack $(String -> Q Exp
forall (m :: * -> *). Quote m => String -> m Exp
stringE (String -> Q Exp) -> String -> Q Exp
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
defaultURL)|],
            Doc -> Q Doc
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Doc -> Q Doc) -> Doc -> Q Doc
forall a b. (a -> b) -> a -> b
$
              [Text] -> Doc
Doc.generateHaddockComment
                [ Text
"The default application name used in the @User-Agent@ header which is based on the @info.title@ field of the specification",
                  Text
"",
                  Text
"@" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
defaultApplicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"@"
                ],
            Decs -> Doc
forall a. Ppr a => a -> Doc
ppr
              (Decs -> Doc) -> Q Decs -> Q Doc
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [d|$(Name -> Q Pat
forall (m :: * -> *). Quote m => Name -> m Pat
varP Name
defaultApplicationNameVarName) = T.pack $(String -> Q Exp
forall (m :: * -> *). Quote m => String -> m Exp
stringE (String -> Q Exp) -> String -> Q Exp
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
defaultApplicationName)|],
            Doc -> Q Doc
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Doc -> Q Doc) -> Doc -> Q Doc
forall a b. (a -> b) -> a -> b
$ [Text] -> Doc
Doc.generateHaddockComment [Text
"The default configuration containing the 'defaultURL' and no authorization"],
            Decs -> Doc
forall a. Ppr a => a -> Doc
ppr (Decs -> Doc) -> Q Decs -> Q Doc
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [d|$(Name -> Q Pat
forall (m :: * -> *). Quote m => Name -> m Pat
varP (Name -> Q Pat) -> Name -> Q Pat
forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"defaultConfiguration") = OC.Configuration $(Name -> Q Exp
forall (m :: * -> *). Quote m => Name -> m Exp
varE Name
defaultURLName) OC.anonymousSecurityScheme True $(Name -> Q Exp
forall (m :: * -> *). Quote m => Name -> m Exp
varE Name
defaultApplicationNameVarName)|]
          ]

-- | Defines all models in the components.schemas section of the 'OAT.OpenApiSpecification'
defineModels :: String -> OAT.OpenApiSpecification -> Dep.Models -> OAM.Generator (Q [Dep.ModuleDefinition])
defineModels :: String
-> OpenApiSpecification
-> Models
-> Generator (Q [ModuleDefinition])
defineModels String
moduleName OpenApiSpecification
spec Models
operationDependencies =
  let schemaDefinitions :: [(Text, Schema)]
schemaDefinitions = Map Text Schema -> [(Text, Schema)]
forall k a. Map k a -> [(k, a)]
Map.toList (Map Text Schema -> [(Text, Schema)])
-> Map Text Schema -> [(Text, Schema)]
forall a b. (a -> b) -> a -> b
$ ComponentsObject -> Map Text Schema
OAT.componentsObjectSchemas (ComponentsObject -> Map Text Schema)
-> ComponentsObject -> Map Text Schema
forall a b. (a -> b) -> a -> b
$ OpenApiSpecification -> ComponentsObject
OAT.openApiSpecificationComponents OpenApiSpecification
spec
   in Text
-> Generator (Q [ModuleDefinition])
-> Generator (Q [ModuleDefinition])
forall a. Text -> Generator a -> Generator a
OAM.nested Text
"components" (Generator (Q [ModuleDefinition])
 -> Generator (Q [ModuleDefinition]))
-> Generator (Q [ModuleDefinition])
-> Generator (Q [ModuleDefinition])
forall a b. (a -> b) -> a -> b
$
        Text
-> Generator (Q [ModuleDefinition])
-> Generator (Q [ModuleDefinition])
forall a. Text -> Generator a -> Generator a
OAM.nested Text
"schemas" (Generator (Q [ModuleDefinition])
 -> Generator (Q [ModuleDefinition]))
-> Generator (Q [ModuleDefinition])
-> Generator (Q [ModuleDefinition])
forall a b. (a -> b) -> a -> b
$ do
          [(Text, Schema)] -> Generator ()
warnAboutUnknownWhiteListedOrOpaqueSchemas [(Text, Schema)]
schemaDefinitions
          [ModelWithDependencies]
models <- ((Text, Schema) -> Generator ModelWithDependencies)
-> [(Text, Schema)] -> Generator [ModelWithDependencies]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM ((Text -> Schema -> Generator ModelWithDependencies)
-> (Text, Schema) -> Generator ModelWithDependencies
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Text -> Schema -> Generator ModelWithDependencies
Model.defineModelForSchema) [(Text, Schema)]
schemaDefinitions
          [Text]
whiteListedSchemas <- (Settings -> [Text]) -> Generator [Text]
forall a. (Settings -> a) -> Generator a
OAM.getSetting Settings -> [Text]
OAO.settingWhiteListedSchemas
          Bool
outputAllSchemas <- (Settings -> Bool) -> Generator Bool
forall a. (Settings -> a) -> Generator a
OAM.getSetting Settings -> Bool
OAO.settingOutputAllSchemas
          let dependencies :: Models
dependencies = Models -> Models -> Models
forall a. Ord a => Set a -> Set a -> Set a
Set.union Models
operationDependencies (Models -> Models) -> Models -> Models
forall a b. (a -> b) -> a -> b
$ [Text] -> Models
forall a. Ord a => [a] -> Set a
Set.fromList ([Text] -> Models) -> [Text] -> Models
forall a b. (a -> b) -> a -> b
$ (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> Text
transformToModuleName [Text]
whiteListedSchemas
          Q [ModuleDefinition] -> Generator (Q [ModuleDefinition])
forall a. a -> Generator a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Q [ModuleDefinition] -> Generator (Q [ModuleDefinition]))
-> Q [ModuleDefinition] -> Generator (Q [ModuleDefinition])
forall a b. (a -> b) -> a -> b
$ String
-> Models
-> Bool
-> [ModelWithDependencies]
-> Q [ModuleDefinition]
Dep.getModelModulesFromModelsWithDependencies String
moduleName Models
dependencies Bool
outputAllSchemas [ModelWithDependencies]
models

-- | Defines all supported security schemes from the 'OAT.OpenApiSpecification'.
defineSecuritySchemes :: String -> OAT.OpenApiSpecification -> OAM.Generator (Q Doc)
defineSecuritySchemes :: String -> OpenApiSpecification -> Generator (Q Doc)
defineSecuritySchemes String
moduleName =
  Text -> Generator (Q Doc) -> Generator (Q Doc)
forall a. Text -> Generator a -> Generator a
OAM.nested Text
"components"
    (Generator (Q Doc) -> Generator (Q Doc))
-> (OpenApiSpecification -> Generator (Q Doc))
-> OpenApiSpecification
-> Generator (Q Doc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Q Doc -> Q Doc) -> Generator (Q Doc) -> Generator (Q Doc)
forall a b. (a -> b) -> Generator a -> Generator b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Doc -> Doc) -> Q Doc -> Q Doc
forall a b. (a -> b) -> Q a -> Q b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Doc -> Doc) -> Q Doc -> Q Doc) -> (Doc -> Doc) -> Q Doc -> Q Doc
forall a b. (a -> b) -> a -> b
$ String -> Doc -> Doc
Doc.addSecuritySchemesModuleHeader String
moduleName)
    (Generator (Q Doc) -> Generator (Q Doc))
-> (OpenApiSpecification -> Generator (Q Doc))
-> OpenApiSpecification
-> Generator (Q Doc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [(Text, SecuritySchemeObject)] -> Generator (Q Doc)
SecurityScheme.defineSupportedSecuritySchemes (String -> Text
T.pack String
moduleName)
    ([(Text, SecuritySchemeObject)] -> Generator (Q Doc))
-> (OpenApiSpecification -> [(Text, SecuritySchemeObject)])
-> OpenApiSpecification
-> Generator (Q Doc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Referencable SecuritySchemeObject)
 -> Maybe (Text, SecuritySchemeObject))
-> [(Text, Referencable SecuritySchemeObject)]
-> [(Text, SecuritySchemeObject)]
forall a b. (a -> Maybe b) -> [a] -> [b]
Maybe.mapMaybe
      ( \(Text
name', Referencable SecuritySchemeObject
scheme') -> case Referencable SecuritySchemeObject
scheme' of
          OAT.Concrete SecuritySchemeObject
s -> (Text, SecuritySchemeObject) -> Maybe (Text, SecuritySchemeObject)
forall a. a -> Maybe a
Just (Text
name', SecuritySchemeObject
s)
          OAT.Reference Text
_ -> Maybe (Text, SecuritySchemeObject)
forall a. Maybe a
Nothing
      )
    ([(Text, Referencable SecuritySchemeObject)]
 -> [(Text, SecuritySchemeObject)])
-> (OpenApiSpecification
    -> [(Text, Referencable SecuritySchemeObject)])
-> OpenApiSpecification
-> [(Text, SecuritySchemeObject)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Text (Referencable SecuritySchemeObject)
-> [(Text, Referencable SecuritySchemeObject)]
forall k a. Map k a -> [(k, a)]
Map.toList
    (Map Text (Referencable SecuritySchemeObject)
 -> [(Text, Referencable SecuritySchemeObject)])
-> (OpenApiSpecification
    -> Map Text (Referencable SecuritySchemeObject))
-> OpenApiSpecification
-> [(Text, Referencable SecuritySchemeObject)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ComponentsObject -> Map Text (Referencable SecuritySchemeObject)
OAT.componentsObjectSecuritySchemes
    (ComponentsObject -> Map Text (Referencable SecuritySchemeObject))
-> (OpenApiSpecification -> ComponentsObject)
-> OpenApiSpecification
-> Map Text (Referencable SecuritySchemeObject)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OpenApiSpecification -> ComponentsObject
OAT.openApiSpecificationComponents