{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} module YamlParse.Applicative.Class where import qualified Data.Aeson as JSON import Data.HashMap.Strict (HashMap) import Data.Int import Data.List.NonEmpty as NE import Data.Map (Map) import Data.Scientific import Data.Set (Set) import qualified Data.Set as S import qualified Data.Text as T import Data.Text (Text) import Data.Vector (Vector) import qualified Data.Vector as V import Data.Word import qualified Data.Yaml as Yaml import GHC.Generics (Generic) import Path import YamlParse.Applicative.Implement import YamlParse.Applicative.Parser -- | A class of types for which a schema is defined. -- -- Note that you do not have to use this class and can just use your own parser values. -- Note also that the parsing of a type of this class should correspond to the parsing of the type in the FromJSON class. class YamlSchema a where {-# MINIMAL yamlSchema #-} -- | A yamlschema for one value -- -- See the sections on helper functions for implementing this for plenty of examples. yamlSchema :: YamlParser a -- | A yamlschema for a list of values -- -- This is really only useful for cases like 'Char' and 'String' yamlSchemaList :: YamlParser [a] yamlSchemaList = V.toList <$> ParseArray Nothing (ParseList yamlSchema) -- | A class of types for which a schema for keys is defined. class YamlKeySchema a where yamlKeySchema :: KeyParser a instance YamlSchema () where yamlSchema = pure () instance YamlSchema Bool where yamlSchema = ParseBool Nothing ParseAny instance YamlSchema Char where yamlSchema = ParseString Nothing $ maybeParser ( \cs -> case T.unpack cs of [] -> Nothing [c] -> Just c _ -> Nothing ) ParseAny yamlSchemaList = T.unpack <$> yamlSchema instance YamlSchema Text where yamlSchema = ParseString Nothing ParseAny instance YamlKeySchema Text where yamlKeySchema = ParseAny instance YamlKeySchema String where yamlKeySchema = T.unpack <$> yamlKeySchema instance YamlSchema Scientific where yamlSchema = ParseNumber Nothing ParseAny instance YamlSchema Int where yamlSchema = boundedIntegerSchema instance YamlSchema Int8 where yamlSchema = boundedIntegerSchema instance YamlSchema Int16 where yamlSchema = boundedIntegerSchema instance YamlSchema Int32 where yamlSchema = boundedIntegerSchema instance YamlSchema Int64 where yamlSchema = boundedIntegerSchema instance YamlSchema Word where yamlSchema = boundedIntegerSchema instance YamlSchema Word8 where yamlSchema = boundedIntegerSchema instance YamlSchema Word16 where yamlSchema = boundedIntegerSchema instance YamlSchema Word32 where yamlSchema = boundedIntegerSchema instance YamlSchema Word64 where yamlSchema = boundedIntegerSchema boundedIntegerSchema :: (Integral i, Bounded i) => YamlParser i boundedIntegerSchema = maybeParser toBoundedInteger $ ParseNumber Nothing ParseAny instance YamlSchema (Path Rel File) where yamlSchema = maybeParser parseRelFile yamlSchema instance YamlSchema (Path Rel Dir) where yamlSchema = maybeParser parseRelDir yamlSchema instance YamlSchema (Path Abs File) where yamlSchema = maybeParser parseAbsFile yamlSchema instance YamlSchema (Path Abs Dir) where yamlSchema = maybeParser parseAbsDir yamlSchema instance YamlSchema Yaml.Value where yamlSchema = ParseAny instance YamlSchema a => YamlSchema (Maybe a) where yamlSchema = ParseMaybe yamlSchema instance YamlSchema a => YamlSchema (Vector a) where yamlSchema = ParseArray Nothing (ParseList yamlSchema) instance YamlSchema a => YamlSchema [a] where yamlSchema = yamlSchemaList instance YamlSchema a => YamlSchema (NonEmpty a) where yamlSchema = extraParser go yamlSchema where go :: [a] -> Yaml.Parser (NonEmpty a) go as = case NE.nonEmpty as of Nothing -> fail "Nonempty list expected, but got an empty list" Just ne -> pure ne instance (Ord a, YamlSchema a) => YamlSchema (Set a) where yamlSchema = S.fromList <$> yamlSchema instance (Ord k, YamlKeySchema k, YamlSchema v) => YamlSchema (Map k v) where yamlSchema = ParseObject Nothing $ ParseMapKeys yamlKeySchema $ ParseMap yamlSchema -- | There is no instance using YamlKeySchema k yet. -- Ideally there wouldn't be one for HashMap Text either because it's insecure, -- but the yaml arrives in a HashMap anyway so we might as well expose this. instance YamlSchema v => YamlSchema (HashMap Text v) where yamlSchema = ParseObject Nothing $ ParseMap yamlSchema -- | A parser for a required field in an object at a given key requiredField :: YamlSchema a => Text -> Text -> ObjectParser a requiredField k h = requiredFieldWith k h yamlSchema -- | A parser for a required field in an object at a given key without a help text requiredField' :: YamlSchema a => Text -> ObjectParser a requiredField' k = requiredFieldWith' k yamlSchema -- | A parser for an optional field in an object at a given key optionalField :: YamlSchema a => Text -> Text -> ObjectParser (Maybe a) optionalField k h = optionalFieldWith k h yamlSchema -- | A parser for an optional field in an object at a given key without a help text optionalField' :: YamlSchema a => Text -> ObjectParser (Maybe a) optionalField' k = optionalFieldWith' k yamlSchema -- | A parser for an optional field in an object at a given key with a default value optionalFieldWithDefault :: (Show a, YamlSchema a) => Text -> a -> Text -> ObjectParser a optionalFieldWithDefault k d h = optionalFieldWithDefaultWith k d h yamlSchema -- | A parser for an optional field in an object at a given key with a default value without a help text optionalFieldWithDefault' :: (Show a, YamlSchema a) => Text -> a -> ObjectParser a optionalFieldWithDefault' k d = optionalFieldWithDefaultWith' k d yamlSchema -- | Helper function to implement 'FromJSON' via 'YamlSchema' -- -- Example: -- -- > instance FromJSON Config where -- > parseJSON = viaYamlSchema viaYamlSchema :: YamlSchema a => Yaml.Value -> Yaml.Parser a viaYamlSchema = implementParser yamlSchema -- | A helper newtype to parse a yaml value using the YamlSchema parser. -- -- Example: -- -- > case Data.Yaml.decodeEither' contents of -- > Left e -> die $ show e -- > Right (ViaYamlSchema res) -> print res -- -- This only helps you when you really don't want to implement a 'FromJSON' instance. -- See 'viaYamlSchema' if you do. newtype ViaYamlSchema a = ViaYamlSchema a deriving (Show, Eq, Generic) instance YamlSchema a => Yaml.FromJSON (ViaYamlSchema a) where parseJSON = fmap ViaYamlSchema . viaYamlSchema