Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions src/Data/OpenApi/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ lowerOpenApiSpecVersion :: Version
lowerOpenApiSpecVersion = makeVersion [3, 0, 0]

-- | This is the upper version of the OpenApi Spec this library can parse or produce
-- Note: Extended to 3.1.x with partial support for 3.1 features (exclusiveMinimum/Maximum as numbers)
upperOpenApiSpecVersion :: Version
upperOpenApiSpecVersion = makeVersion [3, 0, 3]
upperOpenApiSpecVersion = makeVersion [3, 1, 99]

-- | The object provides metadata about the API.
-- The metadata MAY be used by the clients if needed,
Expand Down Expand Up @@ -627,6 +628,24 @@ type Format = Text

type ParamName = Text

-- | Exclusive bound for minimum/maximum values.
-- In OpenAPI 3.0, this is a Bool indicating whether the bound is exclusive.
-- In OpenAPI 3.1, this is a Scientific value representing the exclusive bound itself.
data ExclusiveBound
= ExclusiveBool Bool -- ^ OpenAPI 3.0 style: true means the minimum/maximum is exclusive
| ExclusiveValue Scientific -- ^ OpenAPI 3.1 style: the actual exclusive bound value
deriving (Eq, Show, Generic, Data, Typeable)

-- | Check if the bound is exclusive (for backward compatibility with 3.0 code)
isExclusive :: ExclusiveBound -> Bool
isExclusive (ExclusiveBool b) = b
isExclusive (ExclusiveValue _) = True

-- | Get the exclusive value if using 3.1 style, Nothing for 3.0 style
exclusiveValue :: ExclusiveBound -> Maybe Scientific
exclusiveValue (ExclusiveValue v) = Just v
exclusiveValue (ExclusiveBool _) = Nothing

data Schema = Schema
{ _schemaTitle :: Maybe Text
, _schemaDescription :: Maybe Text
Expand Down Expand Up @@ -662,9 +681,9 @@ data Schema = Schema
, _schemaFormat :: Maybe Format
, _schemaItems :: Maybe OpenApiItems
, _schemaMaximum :: Maybe Scientific
, _schemaExclusiveMaximum :: Maybe Bool
, _schemaExclusiveMaximum :: Maybe ExclusiveBound
, _schemaMinimum :: Maybe Scientific
, _schemaExclusiveMinimum :: Maybe Bool
, _schemaExclusiveMinimum :: Maybe ExclusiveBound
, _schemaMaxLength :: Maybe Integer
, _schemaMinLength :: Maybe Integer
, _schemaPattern :: Maybe Pattern
Expand Down Expand Up @@ -1181,6 +1200,11 @@ instance ToJSON Style where
instance ToJSON OpenApiType where
toJSON = genericToJSON (jsonPrefix "Swagger")

-- | Serialize ExclusiveBound - uses 3.1 style (Number) for values, 3.0 style (Bool) for bools
instance ToJSON ExclusiveBound where
toJSON (ExclusiveBool b) = Bool b
toJSON (ExclusiveValue n) = Number n

instance ToJSON ParamLocation where
toJSON = genericToJSON (jsonPrefix "Param")

Expand Down Expand Up @@ -1236,6 +1260,12 @@ instance FromJSON Style where
instance FromJSON OpenApiType where
parseJSON = genericParseJSON (jsonPrefix "Swagger")

-- | Parse ExclusiveBound from either Bool (3.0) or Number (3.1)
instance FromJSON ExclusiveBound where
parseJSON (Bool b) = pure (ExclusiveBool b)
parseJSON (Number n) = pure (ExclusiveValue n)
parseJSON _ = empty

instance FromJSON ParamLocation where
parseJSON = genericParseJSON (jsonPrefix "Param")

Expand Down
2 changes: 1 addition & 1 deletion src/Data/OpenApi/Internal/ParamSchema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ instance ToParamSchema Natural where
toParamSchema _ = mempty
& type_ ?~ OpenApiInteger
& minimum_ ?~ 0
& exclusiveMinimum ?~ False
& exclusiveMinimum ?~ ExclusiveBool False

instance ToParamSchema Int where toParamSchema = toParamSchemaBoundedIntegral
instance ToParamSchema Int8 where toParamSchema = toParamSchemaBoundedIntegral
Expand Down
30 changes: 24 additions & 6 deletions src/Data/OpenApi/Internal/Schema/Validation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -319,16 +319,34 @@ validateInteger n = do

validateNumber :: Scientific -> Validation Schema ()
validateNumber n = withConfig $ \_cfg -> withSchema $ \sch -> do
let exMax = Just True == sch ^. exclusiveMaximum
exMin = Just True == sch ^. exclusiveMinimum
-- Handle OpenAPI 3.0 style (exclusiveMinimum/Maximum as Bool modifying minimum/maximum)
let exMaxBool = case sch ^. exclusiveMaximum of
Just (ExclusiveBool True) -> True
_ -> False
exMinBool = case sch ^. exclusiveMinimum of
Just (ExclusiveBool True) -> True
_ -> False

-- Handle OpenAPI 3.1 style (exclusiveMinimum/Maximum as the actual bound value)
case sch ^. exclusiveMaximum of
Just (ExclusiveValue m) ->
when (n >= m) $
invalid ("value " ++ show n ++ " exceeds exclusive maximum (should be <" ++ show m ++ ")")
_ -> pure ()

case sch ^. exclusiveMinimum of
Just (ExclusiveValue m) ->
when (n <= m) $
invalid ("value " ++ show n ++ " falls below exclusive minimum (should be >" ++ show m ++ ")")
_ -> pure ()

check maximum_ $ \m ->
when (if exMax then (n >= m) else (n > m)) $
invalid ("value " ++ show n ++ " exceeds maximum (should be " ++ (if exMax then "<" else "<=") ++ show m ++ ")")
when (if exMaxBool then (n >= m) else (n > m)) $
invalid ("value " ++ show n ++ " exceeds maximum (should be " ++ (if exMaxBool then "<" else "<=") ++ show m ++ ")")

check minimum_ $ \m ->
when (if exMin then (n <= m) else (n < m)) $
invalid ("value " ++ show n ++ " falls below minimum (should be " ++ (if exMin then ">" else ">=") ++ show m ++ ")")
when (if exMinBool then (n <= m) else (n < m)) $
invalid ("value " ++ show n ++ " falls below minimum (should be " ++ (if exMinBool then ">" else ">=") ++ show m ++ ")")

check multipleOf $ \k ->
when (not (isInteger (n / k))) $
Expand Down
4 changes: 2 additions & 2 deletions src/Data/OpenApi/Lens.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ instance
maximum_ = schema.maximum_

instance {-# OVERLAPPABLE #-} HasSchema s Schema
=> HasExclusiveMaximum s (Maybe Bool) where
=> HasExclusiveMaximum s (Maybe ExclusiveBound) where
exclusiveMaximum = schema.exclusiveMaximum

instance {-# OVERLAPPABLE #-} HasSchema s Schema
=> HasMinimum s (Maybe Scientific) where
minimum_ = schema.minimum_

instance {-# OVERLAPPABLE #-} HasSchema s Schema
=> HasExclusiveMinimum s (Maybe Bool) where
=> HasExclusiveMinimum s (Maybe ExclusiveBound) where
exclusiveMinimum = schema.exclusiveMinimum

instance {-# OVERLAPPABLE #-} HasSchema s Schema
Expand Down
4 changes: 2 additions & 2 deletions src/Data/OpenApi/Optics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ instance
-- #exclusiveMaximum

instance
( a ~ Maybe Bool, b ~ Maybe Bool
( a ~ Maybe ExclusiveBound, b ~ Maybe ExclusiveBound
) => LabelOptic "exclusiveMaximum" A_Lens NamedSchema NamedSchema a b where
labelOptic = #schema % #exclusiveMaximum
{-# INLINE labelOptic #-}
Expand All @@ -259,7 +259,7 @@ instance
-- #exclusiveMinimum

instance
( a ~ Maybe Bool, b ~ Maybe Bool
( a ~ Maybe ExclusiveBound, b ~ Maybe ExclusiveBound
) => LabelOptic "exclusiveMinimum" A_Lens NamedSchema NamedSchema a b where
labelOptic = #schema % #exclusiveMinimum
{-# INLINE labelOptic #-}
Expand Down