openapi3-code-generator-0.1.0.0: src/OpenAPI/Generate/ModelDependencies.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
-- | Functionality to split models into multiple modules according to their dependencies
module OpenAPI.Generate.ModelDependencies
( getModelModulesFromModelsWithDependencies,
ModuleDefinition,
Models,
ModelContentWithDependencies,
ModelWithDependencies,
)
where
import Data.List
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text (Text)
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"
cyclicTypesModule :: String
cyclicTypesModule = "CyclicTypes"
-- | Analyzes the dependencies of the provided models and splits them into modules.
-- All models with cyclic dependencies (between each other or to itself) are put in a module named by @cyclicTypesModule@.
getModelModulesFromModelsWithDependencies :: String -> [ModelWithDependencies] -> Q [ModuleDefinition]
getModelModulesFromModelsWithDependencies mainModuleName = createModelModules mainModuleName . extractCyclicModuleDependentModels
createModelModules :: String -> ([ModelWithDependencies], Q Doc) -> Q [ModuleDefinition]
createModelModules mainModuleName (models, cyclicModuleContentQ) = do
let prependTypesModule = ((typesModule <> ".") <>) . T.unpack
let prependMainModule = ((mainModuleName <> ".") <>)
cyclicModuleContent <- cyclicModuleContentQ
modules <-
mapM
( \(modelName, (doc, dependencies)) ->
([typesModule, T.unpack modelName],)
. Doc.addModelModuleHeader
mainModuleName
(prependTypesModule modelName)
(prependTypesModule <$> Set.toList dependencies)
("Contains the types generated from the schema " <> T.unpack modelName)
<$> doc
)
models
let modelModuleNames = fmap (joinWithPoint . fst) modules
pure $
( [typesModule],
Doc.createModuleHeaderWithReexports
(prependMainModule typesModule)
(fmap prependMainModule (cyclicTypesModule : modelModuleNames))
"Rexports all type modules (used in the operation modules)."
)
: ( [cyclicTypesModule],
Doc.addModelModuleHeader
mainModuleName
cyclicTypesModule
modelModuleNames
"Contains all types with cyclic dependencies (between each other or to itself)"
cyclicModuleContent
)
: modules
extractCyclicModuleDependentModels :: [ModelWithDependencies] -> ([ModelWithDependencies], Q Doc)
extractCyclicModuleDependentModels models =
let (cyclicModels, extractedModels) = extractUnidirectionallyDependentModels (models, [])
in (extractedModels, vcat <$> mapM (fst . snd) cyclicModels)
extractUnidirectionallyDependentModels :: ([ModelWithDependencies], [ModelWithDependencies]) -> ([ModelWithDependencies], [ModelWithDependencies])
extractUnidirectionallyDependentModels (rest, extractedModels) =
let extractedModelNames = Set.fromList $ fmap fst extractedModels
(newExtractedModels, notExtractedModels) = partition ((`Set.isSubsetOf` extractedModelNames) . snd . snd) rest
in if null newExtractedModels
then (rest, extractedModels)
else extractUnidirectionallyDependentModels (notExtractedModels, extractedModels <> newExtractedModels)