swarm-0.5.0.0: src/Swarm/Doc/Schema/Refined.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Refined JSON schema after converting
-- all JSON Value types to their specific sum types
module Swarm.Doc.Schema.Refined where
import Control.Applicative ((<|>))
import Data.Aeson
import Data.List.Extra (replace)
import Data.Map (Map)
import Data.Map qualified as M
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics (Generic)
import Swarm.Doc.Schema.SchemaType
import System.FilePath (takeBaseName)
import Text.Pandoc
import Text.Pandoc.Builder
-- * Basic
schemaJsonOptions :: Options
schemaJsonOptions =
defaultOptions
{ fieldLabelModifier = replace "S" "$" . drop 1 -- drops leading underscore
}
-- | A single record that encompasses all possible objects
-- in a JSON schema. All fields are optional.
data SchemaRaw = SchemaRaw
{ _description :: Maybe Text
, _default :: Maybe Value
, _title :: Maybe Text
, _type :: Maybe (SingleOrList Text)
, _name :: Maybe Text
, _properties :: Maybe (Map Text SwarmSchema)
, _items :: Maybe (ItemDescription SwarmSchema)
, _examples :: Maybe [Value]
, _Sref :: Maybe Text
, _oneOf :: Maybe [SchemaRaw]
, _footers :: Maybe [FilePath]
, _additionalProperties :: Maybe Bool
}
deriving (Eq, Ord, Show, Generic)
instance FromJSON SchemaRaw where
parseJSON = genericParseJSON schemaJsonOptions
extractSchemaType :: SchemaRaw -> Maybe SchemaType
extractSchemaType rawSchema =
mkReference <$> _Sref rawSchema
<|> getTypeFromItems
<|> Simple <$> _type rawSchema
<|> Alternatives . mapMaybe extractSchemaType <$> _oneOf rawSchema
where
mkReference = Reference . SchemaIdReference . T.pack . takeBaseName . T.unpack
getTypeFromItems :: Maybe SchemaType
getTypeFromItems = do
itemsThing <- _items rawSchema
case itemsThing of
ItemList _ -> Nothing
ItemType x -> Just $ ListOf $ schemaType x
-- * Refined
data ItemDescription a
= ItemList [a]
| ItemType a
deriving (Eq, Ord, Show)
instance (FromJSON a) => FromJSON (ItemDescription a) where
parseJSON x =
ItemList <$> parseJSON x
<|> ItemType <$> parseJSON x
getSchemaReferences :: SchemaType -> [SchemaIdReference]
getSchemaReferences = \case
Simple _ -> []
Alternatives xs -> concatMap getSchemaReferences xs
Reference x -> pure x
ListOf x -> getSchemaReferences x
-- | A subset of all JSON schemas, conforming to internal Swarm conventions.
--
-- Conveniently, this extra representation layer
-- is able to enforce (via 'toSwarmSchema') that all "object"
-- definitions in the schema contain the @"additionalProperties": true@ attribute.
data SwarmSchema = SwarmSchema
{ schemaType :: SchemaType
, defaultValue :: Maybe Value
, objectDescription :: Maybe Pandoc
, properties :: Maybe (Map Text SwarmSchema)
, itemsDescription :: Maybe (ItemDescription SwarmSchema)
, examples :: [Value]
}
deriving (Eq, Ord, Show)
instance FromJSON SwarmSchema where
parseJSON x = do
rawSchema :: rawSchema <- parseJSON x
toSwarmSchema rawSchema
getMarkdown :: MonadFail m => Text -> m Pandoc
getMarkdown desc = case runPure (readMarkdown def desc) of
Right d -> return d
Left err -> fail $ T.unpack $ renderError err
toSwarmSchema :: MonadFail m => SchemaRaw -> m SwarmSchema
toSwarmSchema rawSchema = do
theType <- maybe (fail "Unspecified sub-schema type") return maybeType
markdownDescription <- mapM getMarkdown $ _description rawSchema
if null (_properties rawSchema) || not (fromMaybe True (_additionalProperties rawSchema))
then return ()
else fail "All objects must specify '\"additionalProperties\": true'"
return
SwarmSchema
{ schemaType = theType
, defaultValue = _default rawSchema
, objectDescription = markdownDescription <|> doc . plain . text <$> _name rawSchema
, examples = fromMaybe [] $ _examples rawSchema
, properties = _properties rawSchema
, itemsDescription = _items rawSchema
}
where
maybeType = extractSchemaType rawSchema
-- * Utilities
-- | Recursively extract references to other schemas
extractReferences :: SwarmSchema -> Set SchemaIdReference
extractReferences s = thisRefList <> otherRefLists
where
thisRefList = Set.fromList . getSchemaReferences $ schemaType s
otherSchemas = maybe [] M.elems $ properties s
otherRefLists = Set.unions $ map extractReferences otherSchemas