packages feed

keiro-dsl-0.4.0.1: test/conformance-codec-compare/Conformance/CodecCompare/Historical.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}

module Conformance.CodecCompare.Historical (
    historicalArtifactInfoCodec,
    generatedEquivalentArtifactInfoCodec,
)
where

import Conformance.Structural.Domain qualified as Domain
import Data.Aeson (Value (..), object, withObject, withText, (.!=), (.:), (.:?), (.=))
import Data.Aeson.Types (Parser, parseEither)
import Data.Text (Text)
import Data.Text qualified as T
import Generated.StructuralConformance.ArtifactCatalog.Codec qualified as GeneratedCodec
import Keiro.Dsl.CodecCompare (HistoricalCodec (..))

historicalArtifactInfoCodec :: HistoricalCodec Domain.ArtifactInfo
historicalArtifactInfoCodec =
    HistoricalCodec
        { hcIdentity = "conformance.structural.ArtifactInfo.aeson"
        , hcVersion = "legacy-v3"
        , hcEncode = encodeArtifactInfo
        , hcDecode = either (Left . T.pack) Right . parseEither parseArtifactInfo
        }

{- | Acceptance control: this stands in for the historical codec after its two
migration quirks have been removed.  Keeping it beside the genuinely
historical codec lets the consumer-level test exercise the runner's success
exit condition as well as its refusal path.
-}
generatedEquivalentArtifactInfoCodec :: HistoricalCodec Domain.ArtifactInfo
generatedEquivalentArtifactInfoCodec =
    HistoricalCodec
        { hcIdentity = "conformance.structural.ArtifactInfo.generated-equivalent"
        , hcVersion = "cutover-v4"
        , hcEncode = GeneratedCodec.encodeArtifactInfoMapped
        , hcDecode = GeneratedCodec.decodeArtifactInfoMapped
        }

encodeArtifactInfo :: Domain.ArtifactInfo -> Value
encodeArtifactInfo value =
    object
        ( [ "artifact_key" .= value.artifactKey
          , "display_name" .= value.displayName
          , "artifact_kind" .= encodeArtifactKind value.artifactKind
          , "location" .= encodeLocation value.location
          , "metadata" .= object ["note" .= value.metadata.note]
          , "active" .= value.active
          , "tags" .= value.tags
          ]
            <> maybe [] (pure . ("artifact_hash" .=)) value.artifactHash
        )

encodeArtifactKind :: Domain.ArtifactKind -> Value
encodeArtifactKind Domain.Guide = String "guide"
encodeArtifactKind Domain.Reference = String "reference"

encodeLocation :: Domain.ArtifactLocation -> Value
encodeLocation location = case location of
    Domain.LocalFile payload -> tagged "local_file" (Just payload)
    Domain.LocalDir payload -> tagged "local_dir" (Just payload)
    Domain.RepoPath payload -> tagged "repo_path" (Just payload)
    Domain.LocUrl payload -> tagged "url" (Just payload)
    Domain.Canonical -> tagged "Canonical" Nothing
  where
    tagged :: Text -> Maybe Text -> Value
    tagged tag payload = object (["tag" .= tag] <> maybe [] (pure . ("contents" .=)) payload)

parseArtifactInfo :: Value -> Parser Domain.ArtifactInfo
parseArtifactInfo = withObject "historical ArtifactInfo" $ \value ->
    Domain.ArtifactInfo
        <$> value .: "artifact_key"
        <*> value .: "display_name"
        <*> value .:? "artifact_hash"
        <*> (value .:? "artifact_kind" >>= maybe (pure Domain.Guide) parseArtifactKind)
        <*> (value .: "location" >>= parseLocation)
        <*> (value .: "metadata" >>= parseMetadata)
        <*> value .:? "active" .!= False
        <*> value .:? "tags" .!= []

parseArtifactKind :: Value -> Parser Domain.ArtifactKind
parseArtifactKind = withText "historical ArtifactKind" $ \value -> case value of
    "guide" -> pure Domain.Guide
    "reference" -> pure Domain.Reference
    _ -> fail "unknown historical artifact kind"

parseLocation :: Value -> Parser Domain.ArtifactLocation
parseLocation = withObject "historical ArtifactLocation" $ \value -> do
    tag <- value .: "tag" :: Parser Text
    case tag of
        "local_file" -> Domain.LocalFile <$> value .: "contents"
        "local_dir" -> Domain.LocalDir <$> value .: "contents"
        "repo_path" -> Domain.RepoPath <$> value .: "contents"
        "url" -> Domain.LocUrl <$> value .: "contents"
        "Canonical" -> pure Domain.Canonical
        _ -> fail "unknown historical artifact location"

parseMetadata :: Value -> Parser Domain.ArtifactMetadata
parseMetadata = withObject "historical ArtifactMetadata" $ \value ->
    Domain.ArtifactMetadata <$> value .:? "note"