packages feed

morpheus-graphql-code-gen-0.22.0: src/Data/Morpheus/CodeGen/Server/Printing/Document.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Data.Morpheus.CodeGen.Server.Printing.Document
  ( renderDocument,
  )
where

import Data.ByteString.Lazy.Char8 (ByteString)
import Data.Morpheus.CodeGen.Internal.AST (CodeGenTypeName (..))
import Data.Morpheus.CodeGen.Printer
  ( Printer (..),
    ignore,
    optional,
    renderExtension,
    renderImport,
    unpack,
  )
import Data.Morpheus.CodeGen.Server.Internal.AST
  ( GQLTypeDefinition (..),
    Kind (..),
    ModuleDefinition (..),
    ServerDeclaration (..),
    ServerDirectiveUsage (..),
    TypeKind,
  )
import Data.Text
  ( pack,
  )
import qualified Data.Text.Lazy as LT
  ( fromStrict,
  )
import Data.Text.Lazy.Encoding (encodeUtf8)
import Prettyprinter
  ( Doc,
    align,
    indent,
    line,
    pretty,
    punctuate,
    tupled,
    vsep,
    (<+>),
  )
import Relude hiding (ByteString, encodeUtf8, optional, print)

renderDocument :: String -> [ServerDeclaration] -> ByteString
renderDocument moduleName types =
  encodeUtf8 $
    LT.fromStrict $
      pack $
        show $
          renderModuleDefinition
            ModuleDefinition
              { moduleName = pack moduleName,
                imports =
                  [ ("Data.Data", ["Typeable"]),
                    ("Data.Morpheus.Kind", ["TYPE"]),
                    ("Data.Morpheus.Types", ["*"]),
                    ("Data.Morpheus", []),
                    ("Data.Text", ["Text"]),
                    ("GHC.Generics", ["Generic"])
                  ],
                extensions =
                  [ "DeriveGeneric",
                    "TypeFamilies",
                    "OverloadedStrings",
                    "DataKinds",
                    "DuplicateRecordFields"
                  ],
                types
              }

renderModuleDefinition :: ModuleDefinition -> Doc n
renderModuleDefinition
  ModuleDefinition
    { extensions,
      moduleName,
      imports,
      types
    } =
    vsep (map renderExtension extensions)
      <> line
      <> line
      <> "module"
      <+> pretty moduleName
      <+> "where"
        <> line
        <> line
        <> vsep (map renderImport imports)
        <> line
        <> line
        <> either (error . show) id (renderTypes types)

type Result = Either Text

renderTypes :: [ServerDeclaration] -> Either Text (Doc ann)
renderTypes = fmap vsep . traverse render

class RenderType a where
  render :: a -> Result (Doc ann)

instance RenderType ServerDeclaration where
  render InterfaceType {} = fail "not supported"
  -- TODO: on scalar we should render user provided type
  render ScalarType {scalarTypeName} =
    pure $ "type" <+> ignore (print scalarTypeName) <+> "= Int"
  render (DataType cgType) = pure (pretty cgType)
  render (GQLTypeInstance gqlType) = pure $ renderGQLType gqlType
  render (GQLDirectiveInstance _) = fail "not supported"

renderTypeableConstraints :: [Text] -> Doc n
renderTypeableConstraints xs = tupled (map (("Typeable" <+>) . pretty) xs) <+> "=>"

defineTypeOptions :: Maybe (TypeKind, Text) -> [Doc n]
defineTypeOptions (Just (kind, tName)) = ["typeOptions _ = dropNamespaceOptions" <+> "(" <> pretty (show kind :: String) <> ")" <+> pretty (show tName :: String)]
defineTypeOptions _ = []

renderGQLType :: GQLTypeDefinition -> Doc ann
renderGQLType gql@GQLTypeDefinition {..}
  | gqlKind == Scalar = ""
  | otherwise =
      "instance"
        <> optional renderTypeableConstraints (typeParameters gqlTarget)
        <+> "GQLType"
        <+> typeHead
        <+> "where"
          <> line
          <> indent 2 (vsep (renderMethods typeHead gql <> defineTypeOptions dropNamespace))
  where
    typeHead = unpack (print gqlTarget)

renderMethods :: Doc n -> GQLTypeDefinition -> [Doc n]
renderMethods typeHead GQLTypeDefinition {..} =
  ["type KIND" <+> typeHead <+> "=" <+> pretty gqlKind]
    <> ["directives _=" <+> renderDirectiveUsages gqlTypeDirectiveUses | not (null gqlTypeDirectiveUses)]

renderDirectiveUsages :: [ServerDirectiveUsage] -> Doc n
renderDirectiveUsages = align . vsep . punctuate " <>" . map pretty