jsons-to-schema-0.1.0.0: src/JSONSchema/Draft4/Internal/Utils.hs
{-|
Module : JSONSchema.Draft4.Internal.Utils
Description : Internal utilities.
Copyright : (c) Gareth Tan, 2017
License : MIT
Assorted internal utilities.
-}
module JSONSchema.Draft4.Internal.Utils
(
alt
, andMaybe
, computeMaximumConstraints
, computeMinimumConstraints
, zipWithPadding
, listToMaybeList
, setToMaybeSet
, parseValue
, printSchema
) where
import Protolude
import qualified Data.Aeson as AE
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Scientific as DS
import qualified Data.Set as DS
import qualified Data.Text.Encoding as TE
import qualified Data.Aeson.Encode.Pretty as AEEP
import qualified JSONSchema.Draft4 as D4
-- |Functions like an Applicative but keeps the 'Just' value if it exists.
alt :: Alternative f => (a -> a -> a) -> f a -> f a -> f a
alt f a b = f <$> a <*> b <|> a <|> b
-- |Make certain functions return Nothing when handed an empty list instead
-- of carrying on with their current behavior
emptyFold :: (Foldable t) => (t a -> a) -> t a -> Maybe a
emptyFold f tma
| null tma = Nothing
| otherwise = Just $ f tma
-- |Returns the and of the Just values or Nothing if there are no Justs
andMaybe :: [Maybe Bool] -> Maybe Bool
andMaybe = emptyFold and . catMaybes
{-| The unification function for the integer constraints @maximum@ and @exclusiveMaximum@.
When unifying schemas the @exclusiveMaximum@ may somestimes need to be modified
based on the @maximum.
-}
computeMaximumConstraints ::
[Maybe DS.Scientific] -> [Maybe Bool] -> (Maybe DS.Scientific, Maybe Bool)
computeMaximumConstraints maxes emaxes =
maximumBy zipComparer (zip maxes emaxes)
-- We use Down to reverse the compare order so that False > True
-- We need False > True because if two schemas have the same maximum
-- but one excludes the maximum then we want the schema to not exclude the
-- maximum (i.e. it is more inclusive)
where
zipComparer (m1, em1) (m2, em2) =
if m1 == m2
then if and $ isNothing <$> [m1, m2] then compare em1 em2 else compare (Down em1) (Down em2)
else compare m1 m2
-- Usually the ordering goes like this: Nothing < Just 20 < Just 30, and so the
-- minimum is Nothing. But we want the ordering to be Just 20 < Just 30 < Nothing
-- (Down would provide the ordering Just 30 < Just 20 < Nothing), so we provide
-- a custom comparison function
{-| The unification function for the integer constraints @minimum@ and @exclusiveMinimum@.
When unifying schemas the @exclusiveMinimum@ may somestimes need to be modified
based on the @minimum.
-}
computeMinimumConstraints ::
[Maybe DS.Scientific] -> [Maybe Bool] -> (Maybe DS.Scientific, Maybe Bool)
computeMinimumConstraints mins emins = minimumBy zipComparer (zip mins emins)
where
justComparer :: (Ord a) => Maybe a -> Maybe a -> Ordering
justComparer (Just x) (Just y) = compare x y
justComparer (Just _) Nothing = LT
justComparer Nothing (Just _) = GT
justComparer Nothing Nothing = EQ
zipComparer (m1, em1) (m2, em2)
=
if m1 == m2
then if and $ isNothing <$> [m1, m2] then compare (Down em1) (Down em2) else compare em1 em2
else justComparer m1 m2
-- This function is from StackOverflow
-- | Zips a list but uses the default value if one list is longer than the other.
zipWithPadding :: a -> b -> [a] -> [b] -> [(a, b)]
zipWithPadding a b (x:xs) (y:ys) = (x, y) : zipWithPadding a b xs ys
zipWithPadding a _ [] ys = zip (repeat a) ys
zipWithPadding _ b xs [] = zip xs (repeat b)
{-| Similar to 'listFromMaybe', but returns the entire list as a
'Just' if it is not empty instead of only the first item. -}
listToMaybeList :: [a] -> Maybe [a]
listToMaybeList [] = Nothing
listToMaybeList xs = Just xs
{-| Returns the entire set as a 'Just' if it is not empty
instead of only one item. -}
setToMaybeSet :: DS.Set a -> Maybe (DS.Set a)
setToMaybeSet s
| DS.null s = Nothing
| otherwise = Just s
{-| Parses a bytestring to a value. -}
parseValue :: BS.ByteString -> AE.Value
parseValue s =
fromMaybe (panic $ "Failed to parse JSON document " <> TE.decodeUtf8 s) .
AE.decode .
BSL.fromStrict $ s
{-| Converts a schema to text. -}
printSchema :: D4.Schema -> Text
printSchema = TE.decodeUtf8 . BSL.toStrict . AEEP.encodePretty . AE.toJSON