openapi3-code-generator-0.2.0.0: src/OpenAPI/Generate/ModelDependencies.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Functionality to split models into multiple modules according to their dependencies
module OpenAPI.Generate.ModelDependencies
( getModelModulesFromModelsWithDependencies,
ModuleDefinition,
Models,
ModelContentWithDependencies,
ModelWithDependencies,
)
where
import Data.List (find, isPrefixOf, partition)
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import Language.Haskell.TH
import Language.Haskell.TH.PprLib hiding ((<>))
import qualified OpenAPI.Generate.Doc as Doc
import OpenAPI.Generate.Internal.Util
-- | A module definition with a name as a string list with the different module levels (e. g. [\"OpenAPI\", \"Generate\"] for "OpenAPI.Generate")
-- and the 'Doc' representing the module content
type ModuleDefinition = ([String], Doc)
-- | A set of model names (typically used as dependency list)
type Models = Set.Set Text
-- | A tuple containing the content and the dependencies of a model
type ModelContentWithDependencies = (Q Doc, Models)
-- | Represents a model with a name, content and dependencies
type ModelWithDependencies = (Text, ModelContentWithDependencies)
typesModule :: String
typesModule = "Types"
-- | Analyzes the dependencies of the provided models and splits them into modules.
-- All models which would form an own module but only consist of a type alias are put in a module named by 'Doc.typeAliasModule'.
getModelModulesFromModelsWithDependencies :: String -> Models -> Bool -> [ModelWithDependencies] -> Q [ModuleDefinition]
getModelModulesFromModelsWithDependencies mainModuleName operationAndWhiteListDependencies outputAllSchemas models = do
let modelsToGenerate =
if outputAllSchemas
then models
else filterRequiredModels operationAndWhiteListDependencies models
prependTypesModule = ((typesModule <> ".") <>) . T.unpack
prependMainModule = ((mainModuleName <> ".") <>)
modelsWithResolvedContent <-
mapM
( \(name, (contentQ, dependencies)) -> do
content <- contentQ
pure (name, (content, dependencies))
)
modelsToGenerate
let (typeAliasModels, modelsWithContent) = partition (\(_, (content, _)) -> isTypeAliasModule content) modelsWithResolvedContent
(typeAliasModuleNames, typeAliasContent, typeAliasDependencies) =
foldr
( \(name, (content, dependencies)) (names, allContent, allDependencies) ->
(Set.insert name names, allContent $$ text "" $$ content, Set.union dependencies allDependencies)
)
(Set.empty, empty, Set.empty)
typeAliasModels
modules =
fmap
( \(modelName, (doc, dependencies)) ->
( [typesModule, T.unpack modelName],
Doc.addModelModuleHeader
mainModuleName
(prependTypesModule modelName)
(prependTypesModule <$> Set.toList (Set.difference dependencies $ Set.insert modelName typeAliasModuleNames))
("Contains the types generated from the schema " <> T.unpack modelName)
doc
)
)
modelsWithContent
modelModuleNames = fmap (joinWithPoint . fst) modules
pure $
( [typesModule],
Doc.createModuleHeaderWithReexports
(prependMainModule typesModule)
(fmap prependMainModule (Doc.typeAliasModule : modelModuleNames))
"Rexports all type modules (used in the operation modules)."
)
: ( [Doc.typeAliasModule],
Doc.addModelModuleHeader
mainModuleName
Doc.typeAliasModule
(prependTypesModule <$> Set.toList (Set.difference typeAliasDependencies typeAliasModuleNames))
"Contains all types with cyclic dependencies (between each other or to itself)"
typeAliasContent
)
: modules
isTypeAliasModule :: Doc -> Bool
isTypeAliasModule =
all
( \l ->
isPrefixOf "--" l
|| isPrefixOf "type" l
|| null l
)
. lines
. show
filterRequiredModels :: Models -> [ModelWithDependencies] -> [ModelWithDependencies]
filterRequiredModels deps models =
let namesOfRequiredModels = resolveRequiredModels deps models
in filter ((`Set.member` namesOfRequiredModels) . fst) models
resolveRequiredModels :: Models -> [ModelWithDependencies] -> Models
resolveRequiredModels deps models =
let newDeps = Set.unions $ snd . snd <$> Maybe.mapMaybe (\dep -> find ((== dep) . fst) models) (Set.toList deps)
in if newDeps `Set.isSubsetOf` deps
then deps
else resolveRequiredModels (Set.union deps newDeps) models