{-| 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