packages feed

keiro-dsl-0.12.0.0: src/Keiro/Dsl/Parser/ReadModel.hs

-- | Read-model syntax.
module Keiro.Dsl.Parser.ReadModel
  ( pReadModel,
  )
where

import Keiro.Dsl.Frontend.Internal (FrontendContext, frontendSupportsFeature)
import Keiro.Dsl.Grammar
import Keiro.Dsl.LanguageVersion (LanguageFeature (MappedConsumerSurfaceSyntax, ProjectionCatalogSyntax, SeparatedProjectionQueryPolicySyntax))
import Keiro.Dsl.Parser.Core
import Keiro.Dsl.Parser.Mapped (pMappedTypeExpr)
import Text.Megaparsec

pReadModel :: FrontendContext -> P ReadModelNode
pReadModel context = do
  loc <- getLoc
  keyword "readmodel"
  name <- ident
  _ <- symbol "{"
  (table, schema) <-
    if frontendSupportsFeature context ProjectionCatalogSyntax
      then option ("", "") $ try $ do
        _ <- symbol "table" *> symbol "="
        table <- stringLit
        _ <- symbol "schema" *> symbol "="
        schema <- stringLit
        pure (table, schema)
      else do
        _ <- symbol "table" *> symbol "="
        table <- stringLit
        _ <- symbol "schema" *> symbol "="
        schema <- stringLit
        pure (table, schema)
  _ <- symbol "columns"
  columns <- braces (many pColumn)
  queryTypes <- optionalLanguageFeature context MappedConsumerSurfaceSyntax "query" pQueryTypes
  _ <- symbol "version" *> symbol "="
  version <- boundedDecimal
  _ <- symbol "shape" *> symbol "="
  shape <- stringLit
  (freshness, supply) <-
    if frontendSupportsFeature context SeparatedProjectionQueryPolicySyntax
      then pSeparatedPolicy
      else pLegacyPolicy
  group <- optionalLanguageFeature context ProjectionCatalogSyntax "group" (try (symbol "group" *> symbol "=" *> ident))
  observedTargets <- case group of
    Nothing -> pure []
    Just _ -> symbol "targets" *> symbol "=" *> brackets (many ident)
  backingTarget <- case group of
    Nothing -> pure Nothing
    Just _ -> optional (symbol "backing" *> symbol "=" *> ident)
  _ <- symbol "}"
  pure
    ReadModelNode
      { rmName = name,
        rmTable = table,
        rmSchema = schema,
        rmColumns = columns,
        rmVersion = version,
        rmShape = shape,
        rmFreshness = freshness,
        rmSupply = supply,
        rmGroup = group,
        rmObservedTargets = observedTargets,
        rmBackingTarget = backingTarget,
        queryTypes,
        rmLoc = loc
      }
  where
    pColumn =
      RmColumn
        <$> wireWord
        <*> ident
        <*> option False (True <$ keyword "required")
    pConsistency = choice [Strong <$ keyword "Strong", Eventual <$ keyword "Eventual"]
    pScope =
      choice
        [ RmEntireLog <$ keyword "entire-log",
          RmCategory <$> (keyword "category" *> stringLit)
        ]
    pFeed = choice [RmInline <$ keyword "inline", RmSubscription <$ keyword "subscription"]
    pLegacyPolicy = do
      separatedMarker <- optional (try (withOwnedSpan (keyword "freshness")))
      case separatedMarker of
        Just marker -> requireLanguageFeatureAt context SeparatedProjectionQueryPolicySyntax (spanOf marker)
        Nothing -> pure ()
      _ <- symbol "consistency" *> symbol "="
      consistency <- pConsistency
      scope <- optional (symbol "scope" *> symbol "=" *> pScope)
      _ <- symbol "feed" *> symbol "="
      feed <- pFeed
      subscription <- optional (symbol "subscription" *> symbol "=" *> stringLit)
      let freshness = case consistency of
            Eventual -> FreshnessImmediate
            Strong -> FreshnessWaitForHead (maybe RmEntireLog id scope)
      pure
        ( freshness,
          LegacyReadModelSupply
            { legacyConsistency = consistency,
              legacyScope = scope,
              legacyFeed = feed,
              legacySubscription = subscription
            }
        )
    pSeparatedPolicy = do
      rejectLegacyPolicyClause
      _ <- symbol "freshness" *> symbol "="
      freshness <-
        choice
          [ FreshnessImmediate <$ keyword "immediate",
            FreshnessWaitForHead <$> (keyword "wait-for-head" *> pScope)
          ]
      rejectLegacyPolicyClause
      pure (freshness, OwnerDerivedSupply)
    rejectLegacyPolicyClause = do
      startOffset <- getOffset
      legacyClause <-
        optional
          ( lookAhead
              ( choice
                  [ "consistency" <$ keyword "consistency",
                    "scope" <$ keyword "scope",
                    "feed" <$ keyword "feed",
                    "subscription" <$ keyword "subscription"
                  ]
              )
          )
      case legacyClause of
        Nothing -> pure ()
        Just clauseName ->
          failAt
            startOffset
            ( "Language 5 readmodel policy is `freshness = immediate | wait-for-head ...`; remove legacy `"
                <> clauseName
                <> "` and derive delivery/subscription from its projection-owner"
            )
    pQueryTypes = do
      inputLoc <- getLoc
      keyword "query"
      _ <- symbol "input" *> symbol "="
      input <- pMappedTypeExpr context
      resultLoc <- getLoc
      keyword "query"
      _ <- symbol "result" *> symbol "="
      result <- pMappedTypeExpr context
      pure ReadModelQueryTypes {input, result, inputLoc, resultLoc}