packages feed

asyncapi3-1.0.0.0: src/Data/AsyncAPI/Internal/Schema.hs

-- | The AsyncAPI 3.1.0 Schema Object: a superset of JSON Schema Draft 07
-- plus @discriminator@, @externalDocs@ and @deprecated@.
module Data.AsyncAPI.Internal.Schema
  ( Schema (..),
    SchemaObj (..),
    emptySchemaObj,
    JsonType (..),
    SchemaType (..),
    ItemsSchema (..),
    AdditionalProperties (..),
  )
where

import Control.Applicative ((<|>))
import Data.Aeson
  ( FromJSON (..),
    ToJSON (..),
    Value (..),
    withObject,
    withText,
    (.:?),
  )
import Data.AsyncAPI.Internal.Encoding
import Data.AsyncAPI.Internal.Referenced
import Data.AsyncAPI.Internal.Types
import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
import Data.HashMap.Strict.InsOrd qualified as InsOrd
import Data.List.NonEmpty (NonEmpty)
import Data.Scientific (Scientific)
import Data.Text (Text)
import GHC.Generics (Generic)

-- | A JSON Schema type name.
--
-- [Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data JsonType
  = JsonNull
  | JsonBoolean
  | JsonObject
  | JsonArray
  | JsonNumber
  | -- | Draft-07 @integer@ (subset of @number@).
    JsonInteger
  | JsonString
  deriving (Eq, Show, Generic)

instance ToJSON JsonType where
  toJSON = jsonViaEncoding
  toEncoding t = toEncoding (jsonTypeText t)

instance FromJSON JsonType where
  parseJSON = withText "JsonType" $ \t ->
    maybe (fail ("unknown JSON type: " <> show t)) pure (jsonTypeFromText t)

jsonTypeText :: JsonType -> Text
jsonTypeText = \case
  JsonNull -> "null"
  JsonBoolean -> "boolean"
  JsonObject -> "object"
  JsonArray -> "array"
  JsonNumber -> "number"
  JsonInteger -> "integer"
  JsonString -> "string"

jsonTypeFromText :: Text -> Maybe JsonType
jsonTypeFromText = \case
  "null" -> Just JsonNull
  "boolean" -> Just JsonBoolean
  "object" -> Just JsonObject
  "array" -> Just JsonArray
  "number" -> Just JsonNumber
  "integer" -> Just JsonInteger
  "string" -> Just JsonString
  _ -> Nothing

-- | The @type@ keyword: a single type or a union of types.
--
-- [Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data SchemaType
  = -- | @type: string@ style.
    SingleType JsonType
  | -- | @type: [string, "null"]@ style.
    MultiType (NonEmpty JsonType)
  deriving (Eq, Show, Generic)

instance ToJSON SchemaType where
  toJSON = jsonViaEncoding
  toEncoding (SingleType t) = toEncoding t
  toEncoding (MultiType ts) = toEncoding ts

instance FromJSON SchemaType where
  parseJSON v = single <|> multi
    where
      single = SingleType <$> parseJSON v
      multi = MultiType <$> parseJSON v

-- | The @items@ keyword: one schema for all items, or per-position schemas.
--
-- [Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data ItemsSchema
  = -- | A single schema applied to every item.
    SingleItem (Referenced Schema)
  | -- | A schema per tuple position.
    ItemList [Referenced Schema]
  deriving (Eq, Show, Generic)

instance ToJSON ItemsSchema where
  toJSON = jsonViaEncoding
  toEncoding (SingleItem s) = toEncoding s
  toEncoding (ItemList ss) = toEncoding ss

instance FromJSON ItemsSchema where
  parseJSON v = single <|> list
    where
      single = SingleItem <$> parseJSON v
      list = ItemList <$> parseJSON v

-- | @additionalProperties@ (and @additionalItems@): an on\/off flag or a
-- schema.
--
-- [Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data AdditionalProperties
  = -- | @additionalProperties: false@ (or @true@).
    AllowAdditional Bool
  | -- | @additionalProperties: {schema}@.
    AdditionalSchema (Referenced Schema)
  deriving (Eq, Show, Generic)

instance ToJSON AdditionalProperties where
  toJSON = jsonViaEncoding
  toEncoding (AllowAdditional b) = toEncoding b
  toEncoding (AdditionalSchema s) = toEncoding s

instance FromJSON AdditionalProperties where
  parseJSON v = bool <|> schema
    where
      bool = AllowAdditional <$> parseJSON v
      schema = AdditionalSchema <$> parseJSON v

-- | A schema: either a JSON Schema boolean or a Schema Object. The empty
-- schema (allowing everything) is @BooleanSchema True@; a schema allowing
-- nothing is @BooleanSchema False@.
--
-- [AsyncAPI 3.1.0 — Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data Schema
  = BooleanSchema Bool
  | SchemaObject SchemaObj
  deriving (Eq, Show, Generic)

instance ToJSON Schema where
  toJSON = jsonViaEncoding
  toEncoding (BooleanSchema b) = toEncoding b
  toEncoding (SchemaObject o) = toEncoding o

instance FromJSON Schema where
  parseJSON (Bool b) = pure (BooleanSchema b)
  parseJSON v = SchemaObject <$> parseJSON v

-- | A Schema Object with no keywords set.
emptySchemaObj :: SchemaObj
emptySchemaObj =
  SchemaObj
    { _schemaTitle = Nothing,
      _schemaType = Nothing,
      _schemaRequired = Nothing,
      _schemaMultipleOf = Nothing,
      _schemaMaximum = Nothing,
      _schemaExclusiveMaximum = Nothing,
      _schemaMinimum = Nothing,
      _schemaExclusiveMinimum = Nothing,
      _schemaMaxLength = Nothing,
      _schemaMinLength = Nothing,
      _schemaPattern = Nothing,
      _schemaMaxItems = Nothing,
      _schemaMinItems = Nothing,
      _schemaUniqueItems = Nothing,
      _schemaMaxProperties = Nothing,
      _schemaMinProperties = Nothing,
      _schemaEnum = Nothing,
      _schemaConst = Nothing,
      _schemaExamples = Nothing,
      _schemaIf = Nothing,
      _schemaThen = Nothing,
      _schemaElse = Nothing,
      _schemaReadOnly = Nothing,
      _schemaWriteOnly = Nothing,
      _schemaProperties = Nothing,
      _schemaPatternProperties = Nothing,
      _schemaAdditionalProperties = Nothing,
      _schemaAdditionalItems = Nothing,
      _schemaItems = Nothing,
      _schemaPropertyNames = Nothing,
      _schemaContains = Nothing,
      _schemaAllOf = Nothing,
      _schemaOneOf = Nothing,
      _schemaAnyOf = Nothing,
      _schemaNot = Nothing,
      _schemaDescription = Nothing,
      _schemaFormat = Nothing,
      _schemaDefault = Nothing,
      _schemaDiscriminator = Nothing,
      _schemaExternalDocs = Nothing,
      _schemaDeprecated = Nothing,
      _schemaExtensions = InsOrd.empty
    }

-- | The Schema Object record. Field order in 'ToJSON' follows the order the
-- keywords are listed in the specification's Properties section, with the
-- AsyncAPI-specific Fixed Fields (@discriminator@, @externalDocs@,
-- @deprecated@) at the end.
--
-- [AsyncAPI 3.1.0 — Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
data SchemaObj = SchemaObj
  { -- | @title@.
    -- [Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObject)
    _schemaTitle :: Maybe Title,
    -- | @type@.
    _schemaType :: Maybe SchemaType,
    -- | @required@.
    _schemaRequired :: Maybe [Text],
    -- | @multipleOf@.
    _schemaMultipleOf :: Maybe Scientific,
    -- | @maximum@.
    _schemaMaximum :: Maybe Scientific,
    -- | @exclusiveMaximum@ (draft-07 numeric form).
    _schemaExclusiveMaximum :: Maybe Scientific,
    -- | @minimum@.
    _schemaMinimum :: Maybe Scientific,
    -- | @exclusiveMinimum@ (draft-07 numeric form).
    _schemaExclusiveMinimum :: Maybe Scientific,
    -- | @maxLength@.
    _schemaMaxLength :: Maybe Int,
    -- | @minLength@.
    _schemaMinLength :: Maybe Int,
    -- | @pattern@.
    _schemaPattern :: Maybe Text,
    -- | @maxItems@.
    _schemaMaxItems :: Maybe Int,
    -- | @minItems@.
    _schemaMinItems :: Maybe Int,
    -- | @uniqueItems@.
    _schemaUniqueItems :: Maybe Bool,
    -- | @maxProperties@.
    _schemaMaxProperties :: Maybe Int,
    -- | @minProperties@.
    _schemaMinProperties :: Maybe Int,
    -- | @enum@.
    _schemaEnum :: Maybe [Value],
    -- | @const@.
    _schemaConst :: Maybe Value,
    -- | @examples@.
    _schemaExamples :: Maybe [Value],
    -- | @if@.
    _schemaIf :: Maybe (Referenced Schema),
    -- | @then@.
    _schemaThen :: Maybe (Referenced Schema),
    -- | @else@.
    _schemaElse :: Maybe (Referenced Schema),
    -- | @readOnly@.
    _schemaReadOnly :: Maybe Bool,
    -- | @writeOnly@.
    _schemaWriteOnly :: Maybe Bool,
    -- | @properties@.
    _schemaProperties :: Maybe (InsOrdHashMap Text (Referenced Schema)),
    -- | @patternProperties@.
    _schemaPatternProperties :: Maybe (InsOrdHashMap Text (Referenced Schema)),
    -- | @additionalProperties@.
    _schemaAdditionalProperties :: Maybe AdditionalProperties,
    -- | @additionalItems@.
    _schemaAdditionalItems :: Maybe AdditionalProperties,
    -- | @items@.
    _schemaItems :: Maybe ItemsSchema,
    -- | @propertyNames@.
    _schemaPropertyNames :: Maybe (Referenced Schema),
    -- | @contains@.
    _schemaContains :: Maybe (Referenced Schema),
    -- | @allOf@.
    -- [Composition and Inheritance](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaComposition)
    _schemaAllOf :: Maybe [Referenced Schema],
    -- | @oneOf@.
    _schemaOneOf :: Maybe [Referenced Schema],
    -- | @anyOf@.
    _schemaAnyOf :: Maybe [Referenced Schema],
    -- | @not@.
    _schemaNot :: Maybe (Referenced Schema),
    -- | @description@ (adjusted: CommonMark allowed).
    _schemaDescription :: Maybe Description,
    -- | @format@ (adjusted: see Data Type Formats).
    -- [Data Type Formats](https://www.asyncapi.com/docs/reference/specification/v3.1.0#dataTypeFormat)
    _schemaFormat :: Maybe Text,
    -- | @default@ (adjusted: MUST conform to the schema's type).
    _schemaDefault :: Maybe Value,
    -- | @discriminator@.
    -- [Schema Object, discriminator](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObjectDiscriminator)
    _schemaDiscriminator :: Maybe Text,
    -- | @externalDocs@.
    -- [Schema Object, externalDocs](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObjectExternalDocs)
    _schemaExternalDocs :: Maybe (Referenced ExternalDocumentation),
    -- | @deprecated@ (default @false@).
    -- [Schema Object, deprecated](https://www.asyncapi.com/docs/reference/specification/v3.1.0#schemaObjectDeprecated)
    _schemaDeprecated :: Maybe Bool,
    -- | Specification extensions (@x-@).
    _schemaExtensions :: InsOrdHashMap Text Value
  }
  deriving (Eq, Show, Generic)

instance ToJSON SchemaObj where
  toJSON = jsonViaEncoding
  toEncoding SchemaObj {..} =
    recordEncoding
      _schemaExtensions
      [ "title" .=? _schemaTitle,
        "type" .=? _schemaType,
        "required" .=? _schemaRequired,
        "multipleOf" .=? _schemaMultipleOf,
        "maximum" .=? _schemaMaximum,
        "exclusiveMaximum" .=? _schemaExclusiveMaximum,
        "minimum" .=? _schemaMinimum,
        "exclusiveMinimum" .=? _schemaExclusiveMinimum,
        "maxLength" .=? _schemaMaxLength,
        "minLength" .=? _schemaMinLength,
        "pattern" .=? _schemaPattern,
        "maxItems" .=? _schemaMaxItems,
        "minItems" .=? _schemaMinItems,
        "uniqueItems" .=? _schemaUniqueItems,
        "maxProperties" .=? _schemaMaxProperties,
        "minProperties" .=? _schemaMinProperties,
        "enum" .=? _schemaEnum,
        "const" .=? _schemaConst,
        "examples" .=? _schemaExamples,
        "if" .=? _schemaIf,
        "then" .=? _schemaThen,
        "else" .=? _schemaElse,
        "readOnly" .=? _schemaReadOnly,
        "writeOnly" .=? _schemaWriteOnly,
        "properties" .=? fmap SortedObject _schemaProperties,
        "patternProperties" .=? fmap SortedObject _schemaPatternProperties,
        "additionalProperties" .=? _schemaAdditionalProperties,
        "additionalItems" .=? _schemaAdditionalItems,
        "items" .=? _schemaItems,
        "propertyNames" .=? _schemaPropertyNames,
        "contains" .=? _schemaContains,
        "allOf" .=? _schemaAllOf,
        "oneOf" .=? _schemaOneOf,
        "anyOf" .=? _schemaAnyOf,
        "not" .=? _schemaNot,
        "description" .=? _schemaDescription,
        "format" .=? _schemaFormat,
        "default" .=? _schemaDefault,
        "discriminator" .=? _schemaDiscriminator,
        "externalDocs" .=? _schemaExternalDocs,
        "deprecated" .=? _schemaDeprecated
      ]

instance FromJSON SchemaObj where
  parseJSON = withObject "SchemaObj" $ \o -> do
    _schemaTitle <- o .:? "title"
    _schemaType <- o .:? "type"
    _schemaRequired <- o .:? "required"
    _schemaMultipleOf <- o .:? "multipleOf"
    _schemaMaximum <- o .:? "maximum"
    _schemaExclusiveMaximum <- o .:? "exclusiveMaximum"
    _schemaMinimum <- o .:? "minimum"
    _schemaExclusiveMinimum <- o .:? "exclusiveMinimum"
    _schemaMaxLength <- o .:? "maxLength"
    _schemaMinLength <- o .:? "minLength"
    _schemaPattern <- o .:? "pattern"
    _schemaMaxItems <- o .:? "maxItems"
    _schemaMinItems <- o .:? "minItems"
    _schemaUniqueItems <- o .:? "uniqueItems"
    _schemaMaxProperties <- o .:? "maxProperties"
    _schemaMinProperties <- o .:? "minProperties"
    _schemaEnum <- o .:? "enum"
    _schemaConst <- o .:? "const"
    _schemaExamples <- o .:? "examples"
    _schemaIf <- o .:? "if"
    _schemaThen <- o .:? "then"
    _schemaElse <- o .:? "else"
    _schemaReadOnly <- o .:? "readOnly"
    _schemaWriteOnly <- o .:? "writeOnly"
    _schemaProperties <- sortedMapField "properties" o
    _schemaPatternProperties <- sortedMapField "patternProperties" o
    _schemaAdditionalProperties <- o .:? "additionalProperties"
    _schemaAdditionalItems <- o .:? "additionalItems"
    _schemaItems <- o .:? "items"
    _schemaPropertyNames <- o .:? "propertyNames"
    _schemaContains <- o .:? "contains"
    _schemaAllOf <- o .:? "allOf"
    _schemaOneOf <- o .:? "oneOf"
    _schemaAnyOf <- o .:? "anyOf"
    _schemaNot <- o .:? "not"
    _schemaDescription <- o .:? "description"
    _schemaFormat <- o .:? "format"
    _schemaDefault <- o .:? "default"
    _schemaDiscriminator <- o .:? "discriminator"
    _schemaExternalDocs <- o .:? "externalDocs"
    _schemaDeprecated <- o .:? "deprecated"
    let _schemaExtensions = parseExtensions o
    pure SchemaObj {..}