{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}

-- | This module contains the 'Generator' monad and functions which deal with this monad.
-- In addition this module contains the means for logging and resolving references since they are
-- closely linked to the 'Generator' monad.
module OpenAPI.Generate.Monad where

import qualified Control.Monad.Reader as MR
import qualified Control.Monad.Writer as MW
import Data.Text (Text)
import qualified OpenAPI.Generate.Log as OAL
import qualified OpenAPI.Generate.OptParse as OAO
import qualified OpenAPI.Generate.Reference as Ref
import qualified OpenAPI.Generate.Types as OAT
import qualified OpenAPI.Generate.Types.Schema as OAS

-- | The reader environment of the 'Generator' monad
--
-- The 'generatorEnvironmentCurrentPath' is updated using the 'nested' function to track the current position within the specification.
-- This is used to produce tracable log messages.
-- The 'generatorEnvironmentReferences' map is a lookup table for references within the OpenAPI specification.
data GeneratorEnvironment = GeneratorEnvironment
  { GeneratorEnvironment -> [Text]
generatorEnvironmentCurrentPath :: [Text],
    GeneratorEnvironment -> ReferenceMap
generatorEnvironmentReferences :: Ref.ReferenceMap,
    GeneratorEnvironment -> Settings
generatorEnvironmentSettings :: OAO.Settings
  }
  deriving (Int -> GeneratorEnvironment -> ShowS
[GeneratorEnvironment] -> ShowS
GeneratorEnvironment -> String
(Int -> GeneratorEnvironment -> ShowS)
-> (GeneratorEnvironment -> String)
-> ([GeneratorEnvironment] -> ShowS)
-> Show GeneratorEnvironment
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> GeneratorEnvironment -> ShowS
showsPrec :: Int -> GeneratorEnvironment -> ShowS
$cshow :: GeneratorEnvironment -> String
show :: GeneratorEnvironment -> String
$cshowList :: [GeneratorEnvironment] -> ShowS
showList :: [GeneratorEnvironment] -> ShowS
Show, GeneratorEnvironment -> GeneratorEnvironment -> Bool
(GeneratorEnvironment -> GeneratorEnvironment -> Bool)
-> (GeneratorEnvironment -> GeneratorEnvironment -> Bool)
-> Eq GeneratorEnvironment
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: GeneratorEnvironment -> GeneratorEnvironment -> Bool
== :: GeneratorEnvironment -> GeneratorEnvironment -> Bool
$c/= :: GeneratorEnvironment -> GeneratorEnvironment -> Bool
/= :: GeneratorEnvironment -> GeneratorEnvironment -> Bool
Eq)

-- | The 'Generator' monad is used to pass a 'MR.Reader' environment to functions in need of resolving references
-- and collects log messages.
newtype Generator a = Generator {forall a.
Generator a -> WriterT [LogEntry] (Reader GeneratorEnvironment) a
unGenerator :: MW.WriterT OAL.LogEntries (MR.Reader GeneratorEnvironment) a}
  deriving ((forall a b. (a -> b) -> Generator a -> Generator b)
-> (forall a b. a -> Generator b -> Generator a)
-> Functor Generator
forall a b. a -> Generator b -> Generator a
forall a b. (a -> b) -> Generator a -> Generator b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
$cfmap :: forall a b. (a -> b) -> Generator a -> Generator b
fmap :: forall a b. (a -> b) -> Generator a -> Generator b
$c<$ :: forall a b. a -> Generator b -> Generator a
<$ :: forall a b. a -> Generator b -> Generator a
Functor, Functor Generator
Functor Generator =>
(forall a. a -> Generator a)
-> (forall a b. Generator (a -> b) -> Generator a -> Generator b)
-> (forall a b c.
    (a -> b -> c) -> Generator a -> Generator b -> Generator c)
-> (forall a b. Generator a -> Generator b -> Generator b)
-> (forall a b. Generator a -> Generator b -> Generator a)
-> Applicative Generator
forall a. a -> Generator a
forall a b. Generator a -> Generator b -> Generator a
forall a b. Generator a -> Generator b -> Generator b
forall a b. Generator (a -> b) -> Generator a -> Generator b
forall a b c.
(a -> b -> c) -> Generator a -> Generator b -> Generator c
forall (f :: * -> *).
Functor f =>
(forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
$cpure :: forall a. a -> Generator a
pure :: forall a. a -> Generator a
$c<*> :: forall a b. Generator (a -> b) -> Generator a -> Generator b
<*> :: forall a b. Generator (a -> b) -> Generator a -> Generator b
$cliftA2 :: forall a b c.
(a -> b -> c) -> Generator a -> Generator b -> Generator c
liftA2 :: forall a b c.
(a -> b -> c) -> Generator a -> Generator b -> Generator c
$c*> :: forall a b. Generator a -> Generator b -> Generator b
*> :: forall a b. Generator a -> Generator b -> Generator b
$c<* :: forall a b. Generator a -> Generator b -> Generator a
<* :: forall a b. Generator a -> Generator b -> Generator a
Applicative, Applicative Generator
Applicative Generator =>
(forall a b. Generator a -> (a -> Generator b) -> Generator b)
-> (forall a b. Generator a -> Generator b -> Generator b)
-> (forall a. a -> Generator a)
-> Monad Generator
forall a. a -> Generator a
forall a b. Generator a -> Generator b -> Generator b
forall a b. Generator a -> (a -> Generator b) -> Generator b
forall (m :: * -> *).
Applicative m =>
(forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
$c>>= :: forall a b. Generator a -> (a -> Generator b) -> Generator b
>>= :: forall a b. Generator a -> (a -> Generator b) -> Generator b
$c>> :: forall a b. Generator a -> Generator b -> Generator b
>> :: forall a b. Generator a -> Generator b -> Generator b
$creturn :: forall a. a -> Generator a
return :: forall a. a -> Generator a
Monad, MR.MonadReader GeneratorEnvironment, MW.MonadWriter OAL.LogEntries)

-- | Runs the generator monad within a provided environment.
runGenerator :: GeneratorEnvironment -> Generator a -> (a, OAL.LogEntries)
runGenerator :: forall a. GeneratorEnvironment -> Generator a -> (a, [LogEntry])
runGenerator GeneratorEnvironment
e (Generator WriterT [LogEntry] (Reader GeneratorEnvironment) a
g) = Reader GeneratorEnvironment (a, [LogEntry])
-> GeneratorEnvironment -> (a, [LogEntry])
forall r a. Reader r a -> r -> a
MR.runReader (WriterT [LogEntry] (Reader GeneratorEnvironment) a
-> Reader GeneratorEnvironment (a, [LogEntry])
forall w (m :: * -> *) a. WriterT w m a -> m (a, w)
MW.runWriterT WriterT [LogEntry] (Reader GeneratorEnvironment) a
g) GeneratorEnvironment
e

-- | Create an environment based on a 'Ref.ReferenceMap' and 'OAO.Settings'
createEnvironment :: OAO.Settings -> Ref.ReferenceMap -> GeneratorEnvironment
createEnvironment :: Settings -> ReferenceMap -> GeneratorEnvironment
createEnvironment Settings
settings ReferenceMap
references =
  GeneratorEnvironment
    { generatorEnvironmentCurrentPath :: [Text]
generatorEnvironmentCurrentPath = [],
      generatorEnvironmentReferences :: ReferenceMap
generatorEnvironmentReferences = ReferenceMap
references,
      generatorEnvironmentSettings :: Settings
generatorEnvironmentSettings = Settings
settings
    }

-- | Writes a log message to a 'Generator' monad
logMessage :: OAL.LogSeverity -> Text -> Generator ()
logMessage :: LogSeverity -> Text -> Generator ()
logMessage LogSeverity
logEntrySeverity Text
logEntryMessage = do
  [Text]
logEntryPath <- Generator [Text]
getCurrentPath
  [LogEntry] -> Generator ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
MW.tell [OAL.LogEntry {[Text]
Text
LogSeverity
logEntrySeverity :: LogSeverity
logEntryMessage :: Text
logEntryPath :: [Text]
logEntryPath :: [Text]
logEntrySeverity :: LogSeverity
logEntryMessage :: Text
..}]

-- | Writes an error to a 'Generator' monad
logError :: Text -> Generator ()
logError :: Text -> Generator ()
logError = LogSeverity -> Text -> Generator ()
logMessage LogSeverity
OAL.ErrorSeverity

-- | Writes a warning to a 'Generator' monad
logWarning :: Text -> Generator ()
logWarning :: Text -> Generator ()
logWarning = LogSeverity -> Text -> Generator ()
logMessage LogSeverity
OAL.WarningSeverity

-- | Writes an info to a 'Generator' monad
logInfo :: Text -> Generator ()
logInfo :: Text -> Generator ()
logInfo = LogSeverity -> Text -> Generator ()
logMessage LogSeverity
OAL.InfoSeverity

-- | Writes a trace to a 'Generator' monad
logTrace :: Text -> Generator ()
logTrace :: Text -> Generator ()
logTrace = LogSeverity -> Text -> Generator ()
logMessage LogSeverity
OAL.TraceSeverity

-- | This function can be used to tell the 'Generator' monad where in the OpenAPI specification the generator currently is
nested :: Text -> Generator a -> Generator a
nested :: forall a. Text -> Generator a -> Generator a
nested Text
pathItem = (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall a.
(GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall r (m :: * -> *) a. MonadReader r m => (r -> r) -> m a -> m a
MR.local ((GeneratorEnvironment -> GeneratorEnvironment)
 -> Generator a -> Generator a)
-> (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a
-> Generator a
forall a b. (a -> b) -> a -> b
$ \GeneratorEnvironment
g ->
  GeneratorEnvironment
g
    { generatorEnvironmentCurrentPath = generatorEnvironmentCurrentPath g <> [pathItem]
    }

-- | This function can be used to tell the 'Generator' monad where in the OpenAPI specification the generator currently is (ignoring any previous path changes)
resetPath :: [Text] -> Generator a -> Generator a
resetPath :: forall a. [Text] -> Generator a -> Generator a
resetPath [Text]
path = (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall a.
(GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall r (m :: * -> *) a. MonadReader r m => (r -> r) -> m a -> m a
MR.local ((GeneratorEnvironment -> GeneratorEnvironment)
 -> Generator a -> Generator a)
-> (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a
-> Generator a
forall a b. (a -> b) -> a -> b
$ \GeneratorEnvironment
g -> GeneratorEnvironment
g {generatorEnvironmentCurrentPath = path}

getCurrentPath :: Generator [Text]
getCurrentPath :: Generator [Text]
getCurrentPath = (GeneratorEnvironment -> [Text]) -> Generator [Text]
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
MR.asks GeneratorEnvironment -> [Text]
generatorEnvironmentCurrentPath

appendToPath :: [Text] -> Generator [Text]
appendToPath :: [Text] -> Generator [Text]
appendToPath [Text]
path = do
  [Text]
p <- Generator [Text]
getCurrentPath
  [Text] -> Generator [Text]
forall a. a -> Generator a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([Text] -> Generator [Text]) -> [Text] -> Generator [Text]
forall a b. (a -> b) -> a -> b
$ [Text]
p [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text]
path

-- | Allows to adjust the settings for certain parts of the generation.
adjustSettings :: (OAO.Settings -> OAO.Settings) -> Generator a -> Generator a
adjustSettings :: forall a. (Settings -> Settings) -> Generator a -> Generator a
adjustSettings Settings -> Settings
f = (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall a.
(GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a -> Generator a
forall r (m :: * -> *) a. MonadReader r m => (r -> r) -> m a -> m a
MR.local ((GeneratorEnvironment -> GeneratorEnvironment)
 -> Generator a -> Generator a)
-> (GeneratorEnvironment -> GeneratorEnvironment)
-> Generator a
-> Generator a
forall a b. (a -> b) -> a -> b
$ \GeneratorEnvironment
g ->
  GeneratorEnvironment
g
    { generatorEnvironmentSettings = f (generatorEnvironmentSettings g)
    }

-- | Helper function to create a lookup function for a specific type
createReferenceLookupM :: (Text -> Ref.ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM :: forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe a
fn Text
key = (GeneratorEnvironment -> Maybe a) -> Generator (Maybe a)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
MR.asks ((GeneratorEnvironment -> Maybe a) -> Generator (Maybe a))
-> (GeneratorEnvironment -> Maybe a) -> Generator (Maybe a)
forall a b. (a -> b) -> a -> b
$ Text -> ReferenceMap -> Maybe a
fn Text
key (ReferenceMap -> Maybe a)
-> (GeneratorEnvironment -> ReferenceMap)
-> GeneratorEnvironment
-> Maybe a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GeneratorEnvironment -> ReferenceMap
generatorEnvironmentReferences

-- | Resolve a 'OAS.SchemaObject' reference from within the 'Generator' monad
getSchemaReferenceM :: Text -> Generator (Maybe OAS.SchemaObject)
getSchemaReferenceM :: Text -> Generator (Maybe SchemaObject)
getSchemaReferenceM = (Text -> ReferenceMap -> Maybe SchemaObject)
-> Text -> Generator (Maybe SchemaObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe SchemaObject
Ref.getSchemaReference

-- | Resolve a 'OAT.ResponseObject' reference from within the 'Generator' monad
getResponseReferenceM :: Text -> Generator (Maybe OAT.ResponseObject)
getResponseReferenceM :: Text -> Generator (Maybe ResponseObject)
getResponseReferenceM = (Text -> ReferenceMap -> Maybe ResponseObject)
-> Text -> Generator (Maybe ResponseObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe ResponseObject
Ref.getResponseReference

-- | Resolve a 'OAT.ParameterObject' reference from within the 'Generator' monad
getParameterReferenceM :: Text -> Generator (Maybe OAT.ParameterObject)
getParameterReferenceM :: Text -> Generator (Maybe ParameterObject)
getParameterReferenceM = (Text -> ReferenceMap -> Maybe ParameterObject)
-> Text -> Generator (Maybe ParameterObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe ParameterObject
Ref.getParameterReference

-- | Resolve a 'OAT.ExampleObject' reference from within the 'Generator' monad
getExampleReferenceM :: Text -> Generator (Maybe OAT.ExampleObject)
getExampleReferenceM :: Text -> Generator (Maybe ExampleObject)
getExampleReferenceM = (Text -> ReferenceMap -> Maybe ExampleObject)
-> Text -> Generator (Maybe ExampleObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe ExampleObject
Ref.getExampleReference

-- | Resolve a 'OAT.RequestBodyObject' reference from within the 'Generator' monad
getRequestBodyReferenceM :: Text -> Generator (Maybe OAT.RequestBodyObject)
getRequestBodyReferenceM :: Text -> Generator (Maybe RequestBodyObject)
getRequestBodyReferenceM = (Text -> ReferenceMap -> Maybe RequestBodyObject)
-> Text -> Generator (Maybe RequestBodyObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe RequestBodyObject
Ref.getRequestBodyReference

-- | Resolve a 'OAT.HeaderObject' reference from within the 'Generator' monad
getHeaderReferenceM :: Text -> Generator (Maybe OAT.HeaderObject)
getHeaderReferenceM :: Text -> Generator (Maybe HeaderObject)
getHeaderReferenceM = (Text -> ReferenceMap -> Maybe HeaderObject)
-> Text -> Generator (Maybe HeaderObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe HeaderObject
Ref.getHeaderReference

-- | Resolve a 'OAT.SecuritySchemeObject' reference from within the 'Generator' monad
getSecuritySchemeReferenceM :: Text -> Generator (Maybe OAT.SecuritySchemeObject)
getSecuritySchemeReferenceM :: Text -> Generator (Maybe SecuritySchemeObject)
getSecuritySchemeReferenceM = (Text -> ReferenceMap -> Maybe SecuritySchemeObject)
-> Text -> Generator (Maybe SecuritySchemeObject)
forall a.
(Text -> ReferenceMap -> Maybe a) -> Text -> Generator (Maybe a)
createReferenceLookupM Text -> ReferenceMap -> Maybe SecuritySchemeObject
Ref.getSecuritySchemeReference

-- | Get all settings passed to the program
getSettings :: Generator OAO.Settings
getSettings :: Generator Settings
getSettings = (GeneratorEnvironment -> Settings) -> Generator Settings
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
MR.asks GeneratorEnvironment -> Settings
generatorEnvironmentSettings

-- | Get a specific setting selected by @f@
getSetting :: (OAO.Settings -> a) -> Generator a
getSetting :: forall a. (Settings -> a) -> Generator a
getSetting Settings -> a
f = (GeneratorEnvironment -> a) -> Generator a
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
MR.asks ((GeneratorEnvironment -> a) -> Generator a)
-> (GeneratorEnvironment -> a) -> Generator a
forall a b. (a -> b) -> a -> b
$ Settings -> a
f (Settings -> a)
-> (GeneratorEnvironment -> Settings) -> GeneratorEnvironment -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GeneratorEnvironment -> Settings
generatorEnvironmentSettings