packages feed

keiro-dsl-0.17.0.0: src/Keiro/Dsl/MappedCodecPlan.hs

{-# OPTIONS_GHC -Werror=incomplete-patterns #-}

-- | Pure lowering plan shared by persisted mapped consumers. The graph is the
-- only schema authority; this module merely plans a consumer type and renders
-- Aeson expressions for either consumer values or generated structural shapes.
module Keiro.Dsl.MappedCodecPlan
  ( MappedAuthorityMode (..),
    MappedReferenceBoundary (..),
    MappedCodecPlan (..),
    MappedCodecPlanError (..),
    planMappedCodec,
    renderMappedEncode,
    renderMappedParse,
  )
where

import Data.Map.Strict qualified as Map
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as T
import Keiro.Dsl.ConsumerTypePlan
import Keiro.Dsl.TypeGraph

data MappedAuthorityMode
  = PrimitiveAuthority
  | ExplicitJsonAuthority
  | StructuralAuthority !MappedKey
  | OpaqueAuthority !MappedKey
  | NominalAuthority !Text
  deriving stock (Eq, Ord, Show)

-- | A consumer root crosses a declared total binding for structural
-- references. A structural-shape field is already on the generated side of
-- that binding and therefore calls the nested shape codec directly.
data MappedReferenceBoundary
  = ConsumerValueBoundary
  | StructuralShapeBoundary
  deriving stock (Eq, Ord, Show)

data MappedCodecPlan = MappedCodecPlan
  { consumerType :: !ConsumerTypePlan,
    resolvedExpression :: !ResolvedTypeExpr,
    authority :: !(Set MappedAuthorityMode)
  }
  deriving stock (Eq, Show)

newtype MappedCodecPlanError
  = MappedCodecConsumerTypeError ConsumerTypePlanError
  deriving stock (Eq, Show)

planMappedCodec :: TypeGraph -> ResolvedTypeExpr -> Either MappedCodecPlanError MappedCodecPlan
planMappedCodec graph expression = do
  plannedType <- either (Left . MappedCodecConsumerTypeError) Right (planConsumerType graph expression)
  pure
    MappedCodecPlan
      { consumerType = plannedType,
        resolvedExpression = expression,
        authority = foldTypeExpr authorityAlgebra expression
      }
  where
    authorityAlgebra =
      TypeExprAlgebra
        { onText = Set.singleton PrimitiveAuthority,
          onInt = Set.singleton PrimitiveAuthority,
          onInteger = Set.singleton PrimitiveAuthority,
          onBool = Set.singleton PrimitiveAuthority,
          onNatural = Set.singleton PrimitiveAuthority,
          onTime = Set.singleton PrimitiveAuthority,
          onJson = Set.singleton ExplicitJsonAuthority,
          onOptional = id,
          onList = id,
          onMap = id,
          onKeyedMap = \key value -> Set.insert (NominalAuthority ((.name) key)) value,
          onRef = \key -> case Map.lookup key ((.declarations) graph) of
            Just ResolvedStructural {} -> Set.singleton (StructuralAuthority key)
            Just ResolvedOpaque {} -> Set.singleton (OpaqueAuthority key)
            Nothing -> error ("keiro-dsl internal invariant: mapped codec plan references missing declaration " <> show key),
          onNominal = Set.singleton . NominalAuthority . (.name)
        }

renderMappedEncode :: TypeGraph -> MappedReferenceBoundary -> MappedCodecPlan -> Text -> Text
renderMappedEncode graph boundary plan = render (0 :: Int) ((.resolvedExpression) plan)
  where
    render depth expression candidate = case expression of
      RText -> primitive candidate
      RInt -> primitive candidate
      RInteger -> primitive candidate
      RBool -> primitive candidate
      RNatural -> primitive candidate
      RTime -> primitive candidate
      RJson -> candidate
      ROptional nested ->
        "maybe Null (\\" <> item depth <> " -> " <> render (depth + 1) nested (item depth) <> ") (" <> candidate <> ")"
      RList nested ->
        "toJSON (map (\\" <> item depth <> " -> " <> render (depth + 1) nested (item depth) <> ") (" <> candidate <> "))"
      RMap nested ->
        "toJSON (Map.map (\\" <> item depth <> " -> " <> render (depth + 1) nested (item depth) <> ") (" <> candidate <> "))"
      RKeyedMap keyLeaf nested ->
        "Object (KeyMap.fromList [(Key.fromText (render"
          <> (.name) keyLeaf
          <> "LeafKey "
          <> key depth
          <> "), "
          <> render (depth + 1) nested (item depth)
          <> ") | ("
          <> key depth
          <> ", "
          <> item depth
          <> ") <- Map.toList ("
          <> candidate
          <> ")])"
      RRef referenceKey -> encodeReference referenceKey candidate
      RNominal leaf -> "encode" <> (.name) leaf <> "Leaf " <> candidate
    primitive candidate = "toJSON (" <> candidate <> ")"
    encodeReference referenceKey candidate = case Map.lookup referenceKey ((.declarations) graph) of
      Just (ResolvedStructural declaration _) ->
        "encode" <> (.name) declaration <> suffix <> argument candidate
      Just ResolvedOpaque {} -> case boundary of
        ConsumerValueBoundary -> "toJSON " <> candidate
        StructuralShapeBoundary -> primitive candidate
      Nothing -> error ("keiro-dsl internal invariant: mapped encoder references missing declaration " <> show referenceKey)
    argument candidate = case boundary of
      ConsumerValueBoundary -> " " <> candidate
      StructuralShapeBoundary -> " (" <> candidate <> ")"
    suffix = case boundary of
      ConsumerValueBoundary -> "Mapped"
      StructuralShapeBoundary -> "Shape"
    item depth = "item" <> tshow depth
    key depth = "key" <> tshow depth

renderMappedParse :: TypeGraph -> MappedReferenceBoundary -> MappedCodecPlan -> Text
renderMappedParse graph boundary plan = render (0 :: Int) ((.resolvedExpression) plan)
  where
    render depth = \case
      RText -> "parseJSON"
      RInt -> "parseJSON"
      RInteger -> "parseJSON"
      RBool -> "parseJSON"
      RNatural -> "parseJSON"
      RTime -> "parseJSON"
      RJson -> "pure"
      ROptional nested ->
        "\\" <> value depth <> " -> case " <> value depth <> " of Null -> pure Nothing; " <> other depth <> " -> Just <$> (" <> render (depth + 1) nested <> ") " <> other depth
      RList nested ->
        "\\" <> value depth <> " -> do " <> items depth <> " <- (parseJSON " <> value depth <> " :: Parser [Value]); traverse (\\(" <> index depth <> ", " <> item depth <> ") -> (" <> render (depth + 1) nested <> ") " <> item depth <> " <?> Index " <> index depth <> ") (zip [0..] " <> items depth <> ")"
      RMap nested ->
        "\\" <> value depth <> " -> do " <> items depth <> " <- (parseJSON " <> value depth <> " :: Parser (Map Text Value)); Map.traverseWithKey (\\" <> key depth <> " " <> item depth <> " -> (" <> render (depth + 1) nested <> ") " <> item depth <> " <?> Key (Key.fromText " <> key depth <> ")) " <> items depth
      RKeyedMap keyLeaf nested ->
        "\\"
          <> value depth
          <> " -> withObject "
          <> tshow ("Map[" <> (.name) keyLeaf <> "]")
          <> " (\\"
          <> object depth
          <> " -> Map.fromList <$> traverse (\\("
          <> rawKey depth
          <> ", "
          <> item depth
          <> ") -> do "
          <> key depth
          <> " <- parse"
          <> (.name) keyLeaf
          <> "LeafKey (Key.toText "
          <> rawKey depth
          <> ") <?> Key "
          <> rawKey depth
          <> "; "
          <> parsedItem depth
          <> " <- ("
          <> render (depth + 1) nested
          <> ") "
          <> item depth
          <> " <?> Key "
          <> rawKey depth
          <> "; pure ("
          <> key depth
          <> ", "
          <> parsedItem depth
          <> ")) (KeyMap.toList "
          <> object depth
          <> ")) "
          <> value depth
      RRef keyValue -> parseReference keyValue
      RNominal leaf -> "parse" <> (.name) leaf <> "Leaf"
    parseReference mappedKey = case Map.lookup mappedKey ((.declarations) graph) of
      Just (ResolvedStructural declaration _) -> "parse" <> (.name) declaration <> suffix
      Just ResolvedOpaque {} -> "parseJSON"
      Nothing -> error ("keiro-dsl internal invariant: mapped parser references missing declaration " <> show mappedKey)
    suffix = case boundary of
      ConsumerValueBoundary -> "Mapped"
      StructuralShapeBoundary -> "Shape"
    value depth = "value" <> tshow depth
    other depth = "other" <> tshow depth
    items depth = "items" <> tshow depth
    index depth = "index" <> tshow depth
    item depth = "item" <> tshow depth
    key depth = "key" <> tshow depth
    rawKey depth = "rawKey" <> tshow depth
    parsedItem depth = "parsedItem" <> tshow depth
    object depth = "object" <> tshow depth

tshow :: (Show value) => value -> Text
tshow = T.pack . show