Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebase on current biocad #3

Merged
merged 7 commits into from
Jun 20, 2024
Merged
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
157 changes: 91 additions & 66 deletions src/Data/OpenApi/Internal/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
Expand All @@ -19,12 +20,13 @@
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-- For TypeErrors
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
{-# LANGUAGE LambdaCase #-}
module Data.OpenApi.Internal.Schema where

import Prelude ()
import Prelude.Compat

import Control.Lens hiding (allOf)
import Control.Lens hiding (allOf, anyOf)
import Data.Data.Lens (template)

import Control.Applicative ((<|>))
Expand Down Expand Up @@ -357,14 +359,16 @@ inlineNonRecursiveSchemas defs = inlineSchemasWhen nonRecursive defs
-- "Jack",
-- 25
-- ],
-- "items": [
-- {
-- "type": "string"
-- },
-- {
-- "type": "number"
-- }
-- ],
-- "items": {
-- "anyOf": [
-- {
-- "type": "string"
-- },
-- {
-- "type": "number"
-- }
-- ]
-- },
-- "type": "array"
-- }
--
Expand Down Expand Up @@ -405,7 +409,7 @@ sketchSchema = sketch . toJSON
& type_ ?~ OpenApiArray
& items ?~ case ischema of
Just s -> OpenApiItemsObject (Inline s)
_ -> OpenApiItemsArray (map Inline ys)
_ -> OpenApiItemsObject (Inline $ mempty & anyOf ?~ (map Inline ys))
where
ys = map go (V.toList xs)
allSame = and ((zipWith (==)) ys (tail ys))
Expand Down Expand Up @@ -441,35 +445,37 @@ sketchSchema = sketch . toJSON
-- 3
-- ]
-- ],
-- "items": [
-- {
-- "enum": [
-- 1
-- ],
-- "maximum": 1,
-- "minimum": 1,
-- "multipleOf": 1,
-- "type": "number"
-- },
-- {
-- "enum": [
-- 2
-- ],
-- "maximum": 2,
-- "minimum": 2,
-- "multipleOf": 2,
-- "type": "number"
-- },
-- {
-- "enum": [
-- 3
-- ],
-- "maximum": 3,
-- "minimum": 3,
-- "multipleOf": 3,
-- "type": "number"
-- }
-- ],
-- "items": {
-- "anyOf": [
-- {
-- "enum": [
-- 1
-- ],
-- "maximum": 1,
-- "minimum": 1,
-- "multipleOf": 1,
-- "type": "number"
-- },
-- {
-- "enum": [
-- 2
-- ],
-- "maximum": 2,
-- "minimum": 2,
-- "multipleOf": 2,
-- "type": "number"
-- },
-- {
-- "enum": [
-- 3
-- ],
-- "maximum": 3,
-- "minimum": 3,
-- "multipleOf": 3,
-- "type": "number"
-- }
-- ]
-- },
-- "maxItems": 3,
-- "minItems": 3,
-- "type": "array",
Expand All @@ -484,26 +490,28 @@ sketchSchema = sketch . toJSON
-- 25
-- ]
-- ],
-- "items": [
-- {
-- "enum": [
-- "Jack"
-- ],
-- "maxLength": 4,
-- "minLength": 4,
-- "pattern": "Jack",
-- "type": "string"
-- },
-- {
-- "enum": [
-- 25
-- ],
-- "maximum": 25,
-- "minimum": 25,
-- "multipleOf": 25,
-- "type": "number"
-- }
-- ],
-- "items": {
-- "anyOf": [
-- {
-- "enum": [
-- "Jack"
-- ],
-- "maxLength": 4,
-- "minLength": 4,
-- "pattern": "Jack",
-- "type": "string"
-- },
-- {
-- "enum": [
-- 25
-- ],
-- "maximum": 25,
-- "minimum": 25,
-- "multipleOf": 25,
-- "type": "number"
-- }
-- ]
-- },
-- "maxItems": 2,
-- "minItems": 2,
-- "type": "array",
Expand Down Expand Up @@ -571,7 +579,7 @@ sketchStrictSchema = go . toJSON
& type_ ?~ OpenApiArray
& maxItems ?~ fromIntegral sz
& minItems ?~ fromIntegral sz
& items ?~ OpenApiItemsArray (map (Inline . go) (V.toList xs))
& items ?~ OpenApiItemsObject (Inline $ mempty & anyOf ?~ (map (Inline . go) (V.toList xs)))
& uniqueItems ?~ allUnique
& enum_ ?~ [js]
where
Expand Down Expand Up @@ -983,20 +991,37 @@ gdeclareSchemaRef opts proxy = do
return $ Ref (Reference name)
_ -> Inline <$> gdeclareSchema opts proxy

appendItem :: Referenced Schema -> Maybe OpenApiItems -> Maybe OpenApiItems
appendItem x Nothing = Just (OpenApiItemsArray [x])
appendItem x (Just (OpenApiItemsArray xs)) = Just (OpenApiItemsArray (xs ++ [x]))
appendItem _ _ = error "GToSchema.appendItem: cannot append to OpenApiItemsObject"
addItem :: Referenced Schema -> Maybe OpenApiItems -> Maybe OpenApiItems
addItem x Nothing = Just (OpenApiItemsArray [x])
addItem x (Just (OpenApiItemsArray xs)) = case xs of
[] -> Just $ OpenApiItemsObject x
[x'] | x == x' -> Just $ OpenApiItemsObject x
_ | x `elem` xs -> Just $ OpenApiItemsObject $ Inline $ mempty & anyOf ?~ xs
_ -> Just $ OpenApiItemsObject $ Inline $ mempty & anyOf ?~ (xs ++ [x])
addItem x (Just (OpenApiItemsObject (Inline s))) =
let appendMaybe = Just . maybe [x] (\xs -> if x `elem` xs then xs else xs ++ [x])
in Just $ OpenApiItemsObject $ Inline $ s & anyOf %~ appendMaybe
addItem x j@(Just (OpenApiItemsObject ref))
| x == ref = j
| otherwise = Just $ OpenApiItemsObject $ Inline $ mempty & anyOf ?~ [ref, x]

withFieldSchema :: forall proxy s f. (Selector s, GToSchema f) =>
SchemaOptions -> proxy s f -> Bool -> Schema -> Declare (Definitions Schema) Schema
withFieldSchema opts _ isRequiredField schema = do
ref <- gdeclareSchemaRef opts (Proxy :: Proxy f)
let setNullable = if isRequiredField
then id
else \case
ref@(Ref _) -> Inline $ mempty & anyOf ?~ [ ref
, Inline $ mempty & nullable ?~ True
& type_ ?~ OpenApiObject
]
Inline s -> Inline $ s & nullable ?~ True
ref <- setNullable <$> gdeclareSchemaRef opts (Proxy :: Proxy f)
return $
if T.null fname
then schema
& type_ ?~ OpenApiArray
& items %~ appendItem ref
& items %~ addItem ref
& maxItems %~ Just . maybe 1 (+1) -- increment maxItems
& minItems %~ Just . maybe 1 (+1) -- increment minItems
else schema
Expand Down
18 changes: 16 additions & 2 deletions src/Data/OpenApi/Internal/Schema/Validation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Prelude ()
import Prelude.Compat

import Control.Applicative
import Control.Lens hiding (allOf)
import Control.Lens hiding (allOf, anyOf)
import Control.Monad (forM, forM_, when)

import Data.Aeson hiding (Result)
Expand Down Expand Up @@ -490,14 +490,28 @@ validateSchemaType val = withSchema $ \sch ->
0 -> invalid $ "Value not valid under any of 'oneOf' schemas: " ++ show val
1 -> valid
_ -> invalid $ "Value matches more than one of 'oneOf' schemas: " ++ show val
(view anyOf -> Just variants) -> do
(asum $ (\var -> validateWithSchemaRef var val) <$> variants)
<|> (invalid $ "Value not valid under any of 'anyOf' schemas: " ++ show val)
(view allOf -> Just variants) -> do
-- Default semantics for Validation Monad will abort when at least one
-- variant does not match.
forM_ variants $ \var ->
validateWithSchemaRef var val

(view not_ -> Just notVariant) -> do
-- Attempt to validate against `notVariant`, expecting it to fail.
-- `False <$ ...` ensures that a successful validation maps to `False`.
-- If the validation fails, `return True` ensures we catch this as the desired outcome.
validationResult <- (False <$ validateWithSchemaRef notVariant val) <|> return True
if validationResult
then valid -- If the result is `True`, it means `validateWithSchemaRef` failed, which is correct.
else invalid $ "Value matches 'not' schema, which it shouldn't: " ++ show val

_ ->
case (sch ^. type_, val) of
-- Type must be set for nullable to have effect
-- See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-20
(Just _, Null) | sch ^. nullable == Just True -> valid
(Just OpenApiNull, Null) -> valid
(Just OpenApiBoolean, Bool _) -> valid
(Just OpenApiInteger, Number n) -> validateInteger n
Expand Down
2 changes: 1 addition & 1 deletion src/Data/OpenApi/Schema/Validation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import Data.OpenApi.Internal.Schema.Validation
-- >>> validateToJSON ([Just "hello", Nothing] :: [Maybe String])
-- ["expected JSON value of type OpenApiString"]
-- >>> validateToJSON (123, Nothing :: Maybe String)
-- ["expected JSON value of type OpenApiString"]
-- ["Value not valid under any of 'anyOf' schemas: Null"]
--
-- However, when @'Maybe' a@ is a type of a record field,
-- validation takes @'required'@ property of the @'Schema'@
Expand Down
22 changes: 20 additions & 2 deletions src/Data/OpenApi/SchemaOptions.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
-- |
-- Module: Data.OpenApi.SchemaOptions
-- Maintainer: Nickolay Kudasov <[email protected]>
-- Stability: experimental
--
-- Generic deriving options for @'ToParamSchema'@ and @'ToSchema'@.
module Data.OpenApi.SchemaOptions where
module Data.OpenApi.SchemaOptions (
SchemaOptions (..)
, defaultSchemaOptions
, fromAesonOptions
) where

import qualified Data.Aeson.Types as Aeson
import Data.Char

-- | Options that specify how to encode your type to Swagger schema.
data SchemaOptions = SchemaOptions
Expand Down Expand Up @@ -40,14 +46,26 @@ data SchemaOptions = SchemaOptions
-- @
defaultSchemaOptions :: SchemaOptions
defaultSchemaOptions = SchemaOptions
-- \x -> traceShowId x
{ fieldLabelModifier = id
, constructorTagModifier = id
, datatypeNameModifier = id
, datatypeNameModifier = conformDatatypeNameModifier
, allNullaryToStringTag = True
, unwrapUnaryRecords = False
, sumEncoding = Aeson.defaultTaggedObject
}


-- | According to spec https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
-- name must conform to ^[a-zA-Z0-9\.\-_]+$
conformDatatypeNameModifier :: String -> String
conformDatatypeNameModifier =
foldl (\acc x -> acc ++ convertChar x) ""
where
convertChar = \case
c | isAlphaNum c || elem c "-._" -> [c]
c -> "_" ++ (show $ ord c) ++ "_"

-- | Convert 'Aeson.Options' to 'SchemaOptions'.
--
-- Specifically the following fields get copied:
Expand Down
Loading
Loading