Skip to content

Commit ac7058e

Browse files
authored
Merge pull request #68 from tonicebrian/master
Fix bug parsing set of valid OpenApi spec versions
2 parents cffc5eb + 78dbcdd commit ac7058e

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

src/Data/OpenApi/Internal.hs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ import Data.OpenApi.Internal.AesonUtils (AesonDefaultValue (..), HasSwaggerAeson
5656
sopSwaggerGenericToJSON, sopSwaggerGenericToJSONWithOpts)
5757
import Data.OpenApi.Internal.Utils
5858
import Generics.SOP.TH (deriveGeneric)
59+
import Data.Version
60+
import Control.Monad (unless)
61+
import Text.ParserCombinators.ReadP (readP_to_S)
5962

6063
-- $setup
6164
-- >>> :seti -XDataKinds
@@ -99,8 +102,19 @@ data OpenApi = OpenApi
99102

100103
-- | Additional external documentation.
101104
, _openApiExternalDocs :: Maybe ExternalDocs
105+
106+
, -- | The spec of OpenApi this spec adheres to. Must be between 'lowerOpenApiSpecVersion' and 'upperOpenApiSpecVersion'
107+
_openApiOpenapi :: OpenApiSpecVersion
102108
} deriving (Eq, Show, Generic, Data, Typeable)
103109

110+
-- | This is the lower version of the OpenApi Spec this library can parse or produce
111+
lowerOpenApiSpecVersion :: Version
112+
lowerOpenApiSpecVersion = makeVersion [3, 0, 0]
113+
114+
-- | This is the upper version of the OpenApi Spec this library can parse or produce
115+
upperOpenApiSpecVersion :: Version
116+
upperOpenApiSpecVersion = makeVersion [3, 0, 3]
117+
104118
-- | The object provides metadata about the API.
105119
-- The metadata MAY be used by the clients if needed,
106120
-- and MAY be presented in editing or documentation generation tools for convenience.
@@ -962,6 +976,8 @@ data AdditionalProperties
962976
| AdditionalPropertiesSchema (Referenced Schema)
963977
deriving (Eq, Show, Data, Typeable)
964978

979+
newtype OpenApiSpecVersion = OpenApiSpecVersion {getVersion :: Version} deriving (Eq, Show, Generic, Data, Typeable)
980+
965981
-------------------------------------------------------------------------------
966982
-- Generic instances
967983
-------------------------------------------------------------------------------
@@ -984,11 +1000,19 @@ deriveGeneric ''OpenApi
9841000
deriveGeneric ''Example
9851001
deriveGeneric ''Encoding
9861002
deriveGeneric ''Link
1003+
deriveGeneric ''OpenApiSpecVersion
9871004

9881005
-- =======================================================================
9891006
-- Monoid instances
9901007
-- =======================================================================
9911008

1009+
instance Semigroup OpenApiSpecVersion where
1010+
(<>) (OpenApiSpecVersion a) (OpenApiSpecVersion b) = OpenApiSpecVersion $ max a b
1011+
1012+
instance Monoid OpenApiSpecVersion where
1013+
mempty = OpenApiSpecVersion (makeVersion [3,0,0])
1014+
mappend = (<>)
1015+
9921016
instance Semigroup OpenApi where
9931017
(<>) = genericMappend
9941018
instance Monoid OpenApi where
@@ -1126,6 +1150,7 @@ instance SwaggerMonoid ExternalDocs
11261150
instance SwaggerMonoid Operation
11271151
instance (Eq a, Hashable a) => SwaggerMonoid (InsOrdHashSet a)
11281152
instance SwaggerMonoid SecurityDefinitions
1153+
instance SwaggerMonoid OpenApiSpecVersion
11291154

11301155
instance SwaggerMonoid MimeList
11311156
deriving instance SwaggerMonoid URL
@@ -1258,6 +1283,9 @@ instance FromJSON OAuth2AuthorizationCodeFlow where
12581283
-- Manual ToJSON instances
12591284
-- =======================================================================
12601285

1286+
instance ToJSON OpenApiSpecVersion where
1287+
toJSON (OpenApiSpecVersion v)= toJSON . showVersion $ v
1288+
12611289
instance ToJSON MediaType where
12621290
toJSON = toJSON . show
12631291
toEncoding = toEncoding . show
@@ -1425,6 +1453,22 @@ instance ToJSON Callback where
14251453
-- Manual FromJSON instances
14261454
-- =======================================================================
14271455

1456+
instance FromJSON OpenApiSpecVersion where
1457+
parseJSON = withText "OpenApiSpecVersion" $ \str ->
1458+
let validatedVersion :: Either String Version
1459+
validatedVersion = do
1460+
parsedVersion <- readVersion str
1461+
unless ((parsedVersion >= lowerOpenApiSpecVersion) && (parsedVersion <= upperOpenApiSpecVersion)) $
1462+
Left ("The provided version " <> showVersion parsedVersion <> " is out of the allowed range >=" <> showVersion lowerOpenApiSpecVersion <> " && <=" <> showVersion upperOpenApiSpecVersion)
1463+
return parsedVersion
1464+
in
1465+
either fail (return . OpenApiSpecVersion) validatedVersion
1466+
where
1467+
readVersion :: Text -> Either String Version
1468+
readVersion v = case readP_to_S parseVersion (Text.unpack v) of
1469+
[] -> Left $ "Failed to parse as a version string " <> Text.unpack v
1470+
solutions -> Right (fst . last $ solutions)
1471+
14281472
instance FromJSON MediaType where
14291473
parseJSON = withText "MediaType" $ \str ->
14301474
maybe (fail $ "Invalid media type literal " <> Text.unpack str) pure $ parseAccept $ encodeUtf8 str
@@ -1594,8 +1638,10 @@ instance HasSwaggerAesonOptions SecurityScheme where
15941638
swaggerAesonOptions _ = mkSwaggerAesonOptions "securityScheme" & saoSubObject ?~ "type"
15951639
instance HasSwaggerAesonOptions Schema where
15961640
swaggerAesonOptions _ = mkSwaggerAesonOptions "schema" & saoSubObject ?~ "paramSchema"
1641+
instance HasSwaggerAesonOptions OpenApiSpecVersion where
1642+
swaggerAesonOptions _ = mkSwaggerAesonOptions "openapi"
15971643
instance HasSwaggerAesonOptions OpenApi where
1598-
swaggerAesonOptions _ = mkSwaggerAesonOptions "swagger" & saoAdditionalPairs .~ [("openapi", "3.0.0")]
1644+
swaggerAesonOptions _ = mkSwaggerAesonOptions "swagger"
15991645
instance HasSwaggerAesonOptions Example where
16001646
swaggerAesonOptions _ = mkSwaggerAesonOptions "example"
16011647
instance HasSwaggerAesonOptions Encoding where
@@ -1604,6 +1650,9 @@ instance HasSwaggerAesonOptions Encoding where
16041650
instance HasSwaggerAesonOptions Link where
16051651
swaggerAesonOptions _ = mkSwaggerAesonOptions "link"
16061652

1653+
instance AesonDefaultValue Version where
1654+
defaultValue = Just (makeVersion [3,0,0])
1655+
instance AesonDefaultValue OpenApiSpecVersion
16071656
instance AesonDefaultValue Server
16081657
instance AesonDefaultValue Components
16091658
instance AesonDefaultValue OAuth2ImplicitFlow

test/Data/OpenApiSpec.hs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ spec = do
3939
describe "OAuth2 Security Definitions with empty Scope" $ oAuth2SecurityDefinitionsEmptyExample <=> oAuth2SecurityDefinitionsEmptyExampleJSON
4040
describe "Composition Schema Example" $ compositionSchemaExample <=> compositionSchemaExampleJSON
4141
describe "Swagger Object" $ do
42-
context "Example with no paths" $ emptyPathsFieldExample <=> emptyPathsFieldExampleJSON
42+
context "Example with no paths" $ do
43+
emptyPathsFieldExample <=> emptyPathsFieldExampleJSON
44+
it "fails to parse a spec with a wrong Openapi spec version" $ do
45+
(fromJSON wrongVersionExampleJSON :: Result OpenApi) `shouldBe` Error "The provided version 3.0.4 is out of the allowed range >=3.0.0 && <=3.0.3"
4346
context "Todo Example" $ swaggerExample <=> swaggerExampleJSON
4447
context "PetStore Example" $ do
4548
it "decodes successfully" $ do
@@ -582,6 +585,16 @@ oAuth2SecurityDefinitionsOpenApi =
582585
emptyPathsFieldExample :: OpenApi
583586
emptyPathsFieldExample = mempty
584587

588+
wrongVersionExampleJSON :: Value
589+
wrongVersionExampleJSON = [aesonQQ|
590+
{
591+
"openapi": "3.0.4",
592+
"info": {"version": "", "title": ""},
593+
"paths": {},
594+
"components": {}
595+
}
596+
|]
597+
585598
emptyPathsFieldExampleJSON :: Value
586599
emptyPathsFieldExampleJSON = [aesonQQ|
587600
{
@@ -695,7 +708,7 @@ swaggerExampleJSON = [aesonQQ|
695708
petstoreExampleJSON :: Value
696709
petstoreExampleJSON = [aesonQQ|
697710
{
698-
"openapi": "3.0.0",
711+
"openapi": "3.0.3",
699712
"info": {
700713
"version": "1.0.0",
701714
"title": "Swagger Petstore",

0 commit comments

Comments
 (0)