packages feed

keiro-dsl-0.6.0.0: test/conformance-aggregate-scalars/Main.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE TypeApplications #-}

module Main (main) where

import Control.Exception (evaluate)
import Control.Monad (forM_, unless)
import Data.Aeson (Result (..), Value (..), object, toJSON, (.=))
import Data.Aeson qualified as Aeson
import Data.Proxy (Proxy (..))
import Data.Time.Calendar (fromGregorian)
import Data.Time.Clock (UTCTime (..), picosecondsToDiffTime)
import Generated.AggregateScalars.ScalarLedger.Codec (encodeScalarLedgerEvent, parseScalarLedgerEvent, scalarLedgerCodec)
import Generated.AggregateScalars.ScalarLedger.Domain
import Generated.AggregateScalars.ScalarLedger.EventStream (scalarLedgerEventStream, scalarLedgerEventStreamDef)
import Generated.AggregateScalars.ScalarLedger.Harness (harnessAssertions)
import Keiki.Builder qualified as B
import Keiki.Core (HsPred, SymTransducer, TransducerValidationWarning (..), ValidationOptions (..), defaultValidationOptions, lit, tadd, validateTransducer, (!), (.>=))
import Keiki.Shape (CanonicalTypeName (..))
import Keiro.Codec (eventType)
import Keiro.EventStream (EventStream (..), StateCodec (..))
import Numeric.Natural (Natural)
import System.Exit (exitFailure)

main :: IO ()
main = do
    _ <- evaluate scalarLedgerEventStream
    let checks =
            harnessAssertions
                <> [ ("event codec preserves picosecond Time and positive Natural", eventRoundTrip)
                   , ("event JSON is exact at picosecond precision", exactEventJson)
                   , ("snapshot codec preserves initial Time and Natural zero", snapshotRoundTrip)
                   , ("Natural JSON accepts zero", naturalJsonAccepts 0)
                   , ("Natural JSON accepts a positive integer", naturalJsonAccepts 7)
                   , ("Natural JSON rejects a negative integer", naturalJsonRejects (Number (-1)))
                   , ("Natural JSON rejects a fractional number", naturalJsonRejects (Number 1.5))
                   , ("Natural canonical type name is stable", canonicalTypeName (Proxy @Natural) == "Natural")
                   , ("Natural arithmetic is structural in Keiki", naturalArithmeticIsStructural)
                   ]
    forM_ checks $ \(label, passed) ->
        putStrLn ((if passed then "PASS  " else "FAIL  ") <> label)
    unless (all snd checks) exitFailure

sampleTime :: UTCTime
sampleTime = UTCTime (fromGregorian 2026 1 2) (picosecondsToDiffTime 11045123456789012)

sampleEvent :: ScalarLedgerEvent
sampleEvent = ScalarsRecorded ScalarsRecordedData{observedAt = sampleTime, revision = 7}

eventRoundTrip :: Bool
eventRoundTrip =
    parseScalarLedgerEvent
        (eventType scalarLedgerCodec sampleEvent)
        (encodeScalarLedgerEvent sampleEvent)
        == Right sampleEvent

exactEventJson :: Bool
exactEventJson =
    encodeScalarLedgerEvent sampleEvent
        == object
            [ "kind" .= ("ScalarsRecorded" :: String)
            , "observedAt" .= sampleTime
            , "revision" .= (7 :: Natural)
            ]

snapshotRoundTrip :: Bool
snapshotRoundTrip = case stateCodec scalarLedgerEventStreamDef of
    Nothing -> False
    Just codec ->
        let encoded = encode codec (initialState scalarLedgerEventStreamDef, initialRegisters scalarLedgerEventStreamDef)
         in case decode codec encoded of
                Left _ -> False
                Right (vertex, registers) ->
                    vertex == ScalarLedgerEmpty
                        && registers ! #observedAt == sampleTime
                        && registers ! #revision == 0
                        && encode codec (vertex, registers) == encoded

naturalJsonAccepts :: Natural -> Bool
naturalJsonAccepts expected = Aeson.fromJSON (toJSON expected) == Success expected

naturalJsonRejects :: Value -> Bool
naturalJsonRejects value = case Aeson.fromJSON value :: Result Natural of
    Error _ -> True
    Success _ -> False

naturalArithmeticIsStructural :: Bool
naturalArithmeticIsStructural = not (any isOpaque warnings)
  where
    warnings =
        validateTransducer
            defaultValidationOptions{warnOpaqueGuards = True}
            naturalArithmeticTransducer
    isOpaque OpaqueGuard{} = True
    isOpaque _ = False

naturalArithmeticTransducer ::
    SymTransducer
        (HsPred ScalarLedgerRegs ScalarLedgerCommand)
        ScalarLedgerRegs
        ScalarLedgerVertex
        ScalarLedgerCommand
        ScalarLedgerEvent
naturalArithmeticTransducer =
    B.buildTransducer ScalarLedgerEmpty initialScalarLedgerRegs (const False) do
        B.from ScalarLedgerEmpty do
            B.onCmd inCtorRecord $ \command -> B.do
                B.requireGuard (tadd command.revision (lit 1) .>= command.revision)
                B.noEmit
                B.goto ScalarLedgerEmpty