packages feed

kansoku-api-0.2.0.0: src/Kansoku/Event/Contract.hs

-- | Stable, serializable publication of the in-source event registry.
module Kansoku.Event.Contract
  ( ContractProperty (..),
    EventVersions (..),
    SchemaCatalog (..),
    VersionedEventSchema (..),
    propertyTypeText,
    schemaCatalog,
  )
where

import Data.Aeson.Types (Options (..))
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Kansoku.Event.Registry
import Kansoku.Event.Types (EventName (..), allowedIdentifierKindNames)
import Kansoku.Prelude

data ContractProperty = ContractProperty
  { name :: !Text,
    propertyType :: !Text,
    required :: !Bool,
    validationPattern :: !(Maybe Text),
    description :: !(Maybe Text),
    minimum :: !(Maybe Int),
    minLength :: !(Maybe Int),
    maxLength :: !(Maybe Int),
    multipleOf :: !(Maybe Int)
  }
  deriving stock (Generic, Eq, Show)

data VersionedEventSchema = VersionedEventSchema
  { version :: !Int,
    note :: !Text,
    properties :: ![ContractProperty]
  }
  deriving stock (Generic, Eq, Show)
  deriving anyclass (FromJSON, ToJSON)

newtype EventVersions = EventVersions
  { versions :: NonEmpty VersionedEventSchema
  }
  deriving stock (Generic, Eq, Show)
  deriving anyclass (FromJSON, ToJSON)

data SchemaCatalog = SchemaCatalog
  { catalogVersion :: !Int,
    service :: !Text,
    identifierKinds :: ![Text],
    events :: !(Map Text EventVersions)
  }
  deriving stock (Generic, Eq, Show)
  deriving anyclass (FromJSON, ToJSON)

contractPropertyOptions :: Options
contractPropertyOptions =
  defaultOptions
    { omitNothingFields = True,
      fieldLabelModifier = \case
        "propertyType" -> "type"
        "validationPattern" -> "pattern"
        field -> field
    }

instance ToJSON ContractProperty where
  toJSON = genericToJSON contractPropertyOptions

instance FromJSON ContractProperty where
  parseJSON = genericParseJSON contractPropertyOptions

propertyTypeText :: PropertyType -> Text
propertyTypeText = \case
  PTString -> "string"
  PTNumber -> "number"
  PTBool -> "bool"
  PTObject -> "object"
  PTArray -> "array"
  PTAny -> "any"

schemaCatalog :: EventRegistry -> SchemaCatalog
schemaCatalog (EventRegistry registry) =
  SchemaCatalog
    { catalogVersion = 5,
      service = "kansoku",
      identifierKinds = allowedIdentifierKindNames,
      events =
        Map.fromList
          [ (unEventName eventName, EventVersions (toContractSchema <$> schemas))
          | (eventName, schemas) <- Map.toAscList registry
          ]
    }

toContractSchema :: EventSchema -> VersionedEventSchema
toContractSchema EventSchema {version, note, properties} =
  VersionedEventSchema
    { version = version,
      note = note,
      properties = toContractProperty <$> properties
    }

toContractProperty :: PropertySpec -> ContractProperty
toContractProperty PropertySpec {name, propertyType, required, validationPattern, description} =
  ContractProperty
    { name = name,
      propertyType = propertyTypeText propertyType,
      required = required,
      validationPattern = case validationPattern of Just SqlNamePattern -> Just (propertyPatternText SqlNamePattern); Just WebVitalMetricPattern -> Just (propertyPatternText WebVitalMetricPattern); _ -> Nothing,
      description = description,
      minimum = case validationPattern of Just NonNegativeNumberPattern -> Just 0; Just NonNegativeIntegerPattern -> Just 0; _ -> Nothing,
      minLength = case validationPattern of Just NonEmptyStringPattern -> Just 1; Just (BoundedStringPattern _) -> Just 1; _ -> Nothing,
      maxLength = case validationPattern of Just (BoundedStringPattern limit) -> Just limit; _ -> Nothing,
      multipleOf = case validationPattern of Just NonNegativeIntegerPattern -> Just 1; _ -> Nothing
    }