keiro-dsl-0.6.0.0: bench/structural-codec/Main.hs
{-# LANGUAGE OverloadedRecordDot #-}
module Main (main) where
import Conformance.Structural.Bindings qualified as Bindings
import Conformance.Structural.Domain qualified as Domain
import Control.DeepSeq (NFData)
import Control.Monad (unless, (>=>))
import Data.Aeson (Value (..), object, withObject, (.!=), (.:), (.:?), (.=))
import Data.Aeson qualified as Aeson
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Aeson.Types (Parser, parseEither)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Text (Text)
import Data.Text qualified as Text
import Generated.StructuralConformance.ArtifactCatalog.Codec (decodeArtifactInfoMapped, encodeArtifactInfoMapped)
import Keiro.Codec.Structural (FixtureCases (..))
import Test.Tasty.Bench (Benchmark, bcompareWithin, bench, bgroup, defaultMain, nf)
main :: IO ()
main = defaultMain benchmarks
benchmarks :: [Benchmark]
benchmarks =
[ bgroup
"encode"
[ comparison "encode-small-record" baselineEncodeArtifact encodeArtifactInfoMapped smallArtifact,
comparison "encode-nested-union" (map baselineEncodeArtifact) (map encodeArtifactInfoMapped) unionArtifacts,
comparison "encode-large-list" (map baselineEncodeArtifact) (map encodeArtifactInfoMapped) largeArtifacts
],
bgroup
"decode"
[ comparison "decode-small-record" baselineDecodeArtifact decodeArtifactInfoMapped smallEncoded,
comparison "decode-nested-union" baselineDecodeArtifacts generatedDecodeArtifacts unionEncoded,
comparison "decode-large-list" baselineDecodeArtifacts generatedDecodeArtifacts largeEncoded
]
]
comparison :: (NFData result) => String -> (input -> result) -> (input -> result) -> input -> Benchmark
comparison label baseline generated input =
bgroup
label
[ bench ("baseline-" <> label) (nf baseline input),
bcompareWithin 0 2 ("baseline-" <> label) $ bench ("generated-" <> label) (nf generated input)
]
allArtifacts :: [Domain.ArtifactInfo]
allArtifacts = map snd (NonEmpty.toList (fixtureCases Bindings.artifactInfoCases))
smallArtifact :: Domain.ArtifactInfo
smallArtifact = snd (NonEmpty.head (fixtureCases Bindings.artifactInfoCases))
unionArtifacts :: [Domain.ArtifactInfo]
unionArtifacts = allArtifacts
largeArtifacts :: [Domain.ArtifactInfo]
largeArtifacts = take 2000 (cycle allArtifacts)
smallEncoded :: Value
smallEncoded = encodeArtifactInfoMapped smallArtifact
unionEncoded :: Value
unionEncoded = Aeson.toJSON (map encodeArtifactInfoMapped unionArtifacts)
largeEncoded :: Value
largeEncoded = Aeson.toJSON (map encodeArtifactInfoMapped largeArtifacts)
generatedDecodeArtifacts :: Value -> Either Text [Domain.ArtifactInfo]
generatedDecodeArtifacts value = do
values <- firstText (parseEither Aeson.parseJSON value)
traverse decodeArtifactInfoMapped values
baselineDecodeArtifacts :: Value -> Either Text [Domain.ArtifactInfo]
baselineDecodeArtifacts = firstText . parseEither (Aeson.parseJSON >=> traverse baselineParseArtifact)
baselineDecodeArtifact :: Value -> Either Text Domain.ArtifactInfo
baselineDecodeArtifact = firstText . parseEither baselineParseArtifact
firstText :: Either String value -> Either Text value
firstText = either (Left . Text.pack) Right
baselineEncodeArtifact :: Domain.ArtifactInfo -> Value
baselineEncodeArtifact value =
object
[ "artifact_key" .= value.artifactKey,
"display_name" .= value.displayName,
"artifact_hash" .= value.artifactHash,
"artifact_kind" .= encodeKind value.artifactKind,
"location" .= encodeLocation value.location,
"metadata" .= object ["note" .= value.metadata.note],
"active" .= value.active,
"tags" .= value.tags
]
baselineParseArtifact :: Value -> Parser Domain.ArtifactInfo
baselineParseArtifact = withObject "ArtifactInfo" $ \value -> do
rejectUnknownFields "ArtifactInfo" ["artifact_key", "display_name", "artifact_hash", "artifact_kind", "location", "metadata", "active", "tags"] value
Domain.ArtifactInfo
<$> value .: "artifact_key"
<*> value .: "display_name"
<*> value .:? "artifact_hash"
<*> (value .:? "artifact_kind" .!= String "guide" >>= parseKind)
<*> (value .: "location" >>= parseLocation)
<*> (value .: "metadata" >>= withObject "ArtifactMetadata" (\metadata -> Domain.ArtifactMetadata <$> metadata .: "note"))
<*> (value .:? "active" .!= False)
<*> (value .:? "tags" .!= [])
encodeKind :: Domain.ArtifactKind -> Value
encodeKind = \case
Domain.Guide -> String "guide"
Domain.Reference -> String "reference"
parseKind :: Value -> Parser Domain.ArtifactKind
parseKind = \case
String "guide" -> pure Domain.Guide
String "reference" -> pure Domain.Reference
_ -> fail "unknown ArtifactKind"
encodeLocation :: Domain.ArtifactLocation -> Value
encodeLocation = \case
Domain.LocalFile path -> tagged "local_file" (Just path)
Domain.LocalDir path -> tagged "local_dir" (Just path)
Domain.RepoPath path -> tagged "repo_path" (Just path)
Domain.LocUrl url -> tagged "url" (Just url)
Domain.Canonical -> tagged "canonical" Nothing
where
tagged :: Text -> Maybe Text -> Value
tagged tag contents = object (["tag" .= tag] <> maybe [] (pure . ("contents" .=)) contents)
parseLocation :: Value -> Parser Domain.ArtifactLocation
parseLocation = withObject "ArtifactLocation" $ \value -> do
tag <- value .: "tag" :: Parser Text
case tag of
"local_file" -> rejectUnknownFields "ArtifactLocation" ["tag", "contents"] value >> (Domain.LocalFile <$> value .: "contents")
"local_dir" -> rejectUnknownFields "ArtifactLocation" ["tag", "contents"] value >> (Domain.LocalDir <$> value .: "contents")
"repo_path" -> rejectUnknownFields "ArtifactLocation" ["tag", "contents"] value >> (Domain.RepoPath <$> value .: "contents")
"url" -> rejectUnknownFields "ArtifactLocation" ["tag", "contents"] value >> (Domain.LocUrl <$> value .: "contents")
"canonical" -> rejectUnknownFields "ArtifactLocation" ["tag"] value >> pure Domain.Canonical
_ -> fail "unknown ArtifactLocation"
rejectUnknownFields :: String -> [Text] -> KeyMap.KeyMap Value -> Parser ()
rejectUnknownFields label allowed value =
unless (null extras) (fail (label <> " contains unknown fields: " <> show extras))
where
extras = filter (`notElem` allowed) (map Key.toText (KeyMap.keys value))