mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
Add request body to OpenAPI
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2632 Co-authored-by: Lyndon Maydwell <92299+sordina@users.noreply.github.com> Co-authored-by: paritosh-08 <85472423+paritosh-08@users.noreply.github.com> GitOrigin-RevId: 067e182effaed255ff047abeee309d65a9fb191c
This commit is contained in:
parent
19c7002a7b
commit
02aef27a75
@ -3,6 +3,8 @@
|
|||||||
## Next release
|
## Next release
|
||||||
(Add highlights/major features below)
|
(Add highlights/major features below)
|
||||||
|
|
||||||
|
- server: add request and response bodies to OpenAPI specification of REST endpoints
|
||||||
|
|
||||||
### Bug fixes and improvements
|
### Bug fixes and improvements
|
||||||
(Add entries below in the order of server, console, cli, docs, others)
|
(Add entries below in the order of server, console, cli, docs, others)
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ library
|
|||||||
, hashable-time
|
, hashable-time
|
||||||
, http-client-tls
|
, http-client-tls
|
||||||
, http-conduit
|
, http-conduit
|
||||||
|
, http-media
|
||||||
, http-types
|
, http-types
|
||||||
, kan-extensions
|
, kan-extensions
|
||||||
, kriti-lang
|
, kriti-lang
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
module Hasura.GraphQL.RemoteServer
|
module Hasura.GraphQL.RemoteServer
|
||||||
( fetchRemoteSchema,
|
( fetchRemoteSchema,
|
||||||
IntrospectionResult,
|
IntrospectionResult,
|
||||||
|
parseIntrospectionResult,
|
||||||
execRemoteGQ,
|
execRemoteGQ,
|
||||||
identityCustomizer,
|
identityCustomizer,
|
||||||
|
introspectionQuery,
|
||||||
-- The following exports are needed for unit tests
|
-- The following exports are needed for unit tests
|
||||||
getCustomizer,
|
getCustomizer,
|
||||||
validateSchemaCustomizationsDistinct,
|
validateSchemaCustomizationsDistinct,
|
||||||
@ -136,6 +138,9 @@ validateSchemaCustomizationsDistinct remoteSchemaCustomizer (RemoteSchemaIntrosp
|
|||||||
<> dquoteList dups
|
<> dquoteList dups
|
||||||
_ -> pure ()
|
_ -> pure ()
|
||||||
|
|
||||||
|
parseIntrospectionResult :: J.Value -> Maybe IntrospectionResult
|
||||||
|
parseIntrospectionResult = fmap fromIntrospection . J.parseMaybe J.parseJSON
|
||||||
|
|
||||||
-- | Make an introspection query to the remote graphql server for the data we
|
-- | Make an introspection query to the remote graphql server for the data we
|
||||||
-- need to present and stitch the remote schema. This powers add_remote_schema,
|
-- need to present and stitch the remote schema. This powers add_remote_schema,
|
||||||
-- and also is called by schema cache rebuilding code in "Hasura.RQL.DDL.Schema.Cache".
|
-- and also is called by schema cache rebuilding code in "Hasura.RQL.DDL.Schema.Cache".
|
||||||
|
@ -1,30 +1,276 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- Module : Hasura.Server.OpenAPI
|
||||||
|
-- Description : Builds an OpenAPI specification for the REST endpoints from a SchemaCache via the `declareOpenApiSpec` function.
|
||||||
|
--
|
||||||
|
-- The implementation currently iterates over the endpoints building up `EndpointData` for each then exposes this as an OpenAPI Schema.
|
||||||
|
--
|
||||||
|
-- Most functions are in the `Declare` monad so that they can add new component definitions on the fly that can be referenced.
|
||||||
|
-- This is especially useful for the params and request body documentation.
|
||||||
|
--
|
||||||
|
-- The response body recurses over the SelectionSet Fields associated with an endpoint and looks up types by name in
|
||||||
|
-- a `RemoteSchemaIntrospection` result generated from the `SchemaCache`.
|
||||||
|
--
|
||||||
|
-- Response bodies are mostly delcared inline, since the associated query will likely be unique and determine the fields
|
||||||
|
-- contained in the response.
|
||||||
module Hasura.Server.OpenAPI (serveJSON) where
|
module Hasura.Server.OpenAPI (serveJSON) where
|
||||||
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Data.Aeson (Value, toJSON)
|
import Data.Aeson qualified as J
|
||||||
import Data.HashMap.Strict qualified as M
|
import Data.Aeson.Ordered qualified as JO
|
||||||
import Data.HashMap.Strict.InsOrd qualified as MI
|
import Data.HashMap.Strict qualified as Map
|
||||||
import Data.List.NonEmpty qualified as LNE
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
||||||
|
import Data.List.NonEmpty qualified as NE
|
||||||
import Data.OpenApi
|
import Data.OpenApi
|
||||||
import Data.OpenApi.Declare
|
import Data.OpenApi.Declare
|
||||||
import Data.Set.Internal qualified as S
|
import Data.Set qualified as Set
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Data.Text.NonEmpty qualified as TNE
|
import Data.Text.Extended (commaSeparated)
|
||||||
|
import Data.Text.NonEmpty
|
||||||
|
import Hasura.GraphQL.Context
|
||||||
|
import Hasura.GraphQL.Namespace (mkUnNamespacedRootFieldAlias)
|
||||||
|
import Hasura.GraphQL.Parser.Schema (Variable)
|
||||||
|
import Hasura.GraphQL.RemoteServer (introspectionQuery, parseIntrospectionResult)
|
||||||
|
import Hasura.GraphQL.Transport.HTTP.Protocol
|
||||||
import Hasura.Prelude hiding (get, put)
|
import Hasura.Prelude hiding (get, put)
|
||||||
|
import Hasura.RQL.IR.Root
|
||||||
import Hasura.RQL.Types.Endpoint
|
import Hasura.RQL.Types.Endpoint
|
||||||
import Hasura.RQL.Types.QueryCollection
|
import Hasura.RQL.Types.QueryCollection
|
||||||
|
import Hasura.RQL.Types.RemoteSchema
|
||||||
import Hasura.RQL.Types.SchemaCache
|
import Hasura.RQL.Types.SchemaCache
|
||||||
|
import Hasura.Session (adminRoleName)
|
||||||
import Language.GraphQL.Draft.Syntax qualified as G
|
import Language.GraphQL.Draft.Syntax qualified as G
|
||||||
|
import Network.HTTP.Media.MediaType ((//))
|
||||||
|
|
||||||
data EndpointData = EndpointData
|
data EndpointData = EndpointData
|
||||||
{ _edUrl :: String,
|
{ _edUrl :: String,
|
||||||
_edMethod :: [Text],
|
_edMethod :: [Text],
|
||||||
_edVarList :: [Referenced Param],
|
_edVarList :: [Referenced Param],
|
||||||
|
_edProperties :: InsOrdHashMap Text (Referenced Schema),
|
||||||
|
_edResponse :: Maybe Response,
|
||||||
_edDescription :: Text, -- contains API comments and graphql query
|
_edDescription :: Text, -- contains API comments and graphql query
|
||||||
_edName :: Text
|
_edName :: Text
|
||||||
}
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
-- * Response Body Related Functions.
|
||||||
|
|
||||||
|
{-
|
||||||
|
|
||||||
|
Example stepthrough initiated by call to getSelectionSchema:
|
||||||
|
|
||||||
|
* Endpoint insert_foo
|
||||||
|
* getExecutableDefinitions -> List (Normally 1 entry)
|
||||||
|
* ExecutableDefinitionOperation -> OperationDefinitionTyped -> TypedOperationDefinition (_todType = OperationTypeMutation)
|
||||||
|
* _todSelectionSet ->
|
||||||
|
[SelectionField
|
||||||
|
(Field{ _fName = Name{unName = "insert_foo"},
|
||||||
|
_fSelectionSet
|
||||||
|
= [SelectionField
|
||||||
|
(Field{ _fName = Name{unName = "returning"},
|
||||||
|
_fSelectionSet
|
||||||
|
= [..., SelectionField
|
||||||
|
(Field{ _fName = Name{unName = "id"},
|
||||||
|
_fSelectionSet = []}),
|
||||||
|
* Lookup introspection schema, Under mutation_root (inferred from operationtype = OperationTypeMutation)
|
||||||
|
RemoteSchemaIntrospection
|
||||||
|
(fromList
|
||||||
|
[..., (Name{unName = "mutation_root"},
|
||||||
|
TypeDefinitionObject
|
||||||
|
(ObjectTypeDefinition{_otdDescription =
|
||||||
|
Just (Description{unDescription = "mutation root"}),
|
||||||
|
_otdName = Name{unName = "mutation_root"},
|
||||||
|
_otdImplementsInterfaces = [], _otdDirectives = [],
|
||||||
|
_otdFieldsDefinition =
|
||||||
|
[..., FieldDefinition{_fldDescription = Just (Description{unDescription = "insert data into the table: \"foo\""}),
|
||||||
|
_fldName = Name{unName = "insert_foo"},
|
||||||
|
_fldType = TypeNamed (Nullability{unNullability = True}) (Name{unName = "foo_mutation_response"}),
|
||||||
|
* Find that type for insert_foo field in mutation_root is named type "foo_mutation_response"
|
||||||
|
* Look up "foo_mutation_response" in introspection schema:
|
||||||
|
(Name{unName = "foo_mutation_response"},
|
||||||
|
TypeDefinitionObject
|
||||||
|
(ObjectTypeDefinition{_otdDescription = Just (Description{unDescription = "response of any mutation on the table \"foo\""}),
|
||||||
|
_otdName = Name{unName = "foo_mutation_response"},
|
||||||
|
_otdFieldsDefinition =
|
||||||
|
[..., FieldDefinition{_fldDescription =
|
||||||
|
Just
|
||||||
|
(Description{unDescription =
|
||||||
|
"data from the rows affected by the mutation"}),
|
||||||
|
_fldName = Name{unName = "returning"},
|
||||||
|
_fldArgumentsDefinition = [],
|
||||||
|
_fldType =
|
||||||
|
TypeList (Nullability{unNullability = False})
|
||||||
|
(TypeNamed
|
||||||
|
(Nullability{unNullability = False})
|
||||||
|
(Name{unName = "foo"})),
|
||||||
|
_fldDirectives = []}]})),
|
||||||
|
* Find first referenced sub-field "returning"
|
||||||
|
* It has type (TypeNamed (Name{unName = "foo"})),
|
||||||
|
* Look up "foo" in Introspection Schema: ...,
|
||||||
|
(Name{unName = "foo"},
|
||||||
|
TypeDefinitionObject
|
||||||
|
(ObjectTypeDefinition{_otdDescription = Just (Description{unDescription = "columns and relationships of \"foo\""}),
|
||||||
|
_otdName = Name{unName = "foo"}, _otdImplementsInterfaces = [],
|
||||||
|
_otdFieldsDefinition =
|
||||||
|
[ ..., FieldDefinition{_fldDescription = Nothing,
|
||||||
|
_fldName = Name{unName = "id"},
|
||||||
|
_fldType = TypeNamed (Nullability{unNullability = False}) (Name{unName = "uuid"}),
|
||||||
|
* Lookup first sub-sub field by SelectionSet field name "id"
|
||||||
|
* See that it has type: TypeNamed (Name{unName = "uuid"})
|
||||||
|
* See that there are no sub-sub-sub fields
|
||||||
|
* declare type uuid by looking up its definition
|
||||||
|
(Name{unName = "uuid"},
|
||||||
|
TypeDefinitionScalar
|
||||||
|
(ScalarTypeDefinition{_stdDescription = Nothing,
|
||||||
|
_stdName = Name{unName = "uuid"}, _stdDirectives = []})),
|
||||||
|
* reference type name from components in output
|
||||||
|
|
||||||
|
... Proceed with other sub-fields and fields
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
mdSelectionFields :: EndpointMetadata GQLQueryWithText -> [(G.OperationType, G.Selection G.FragmentSpread G.Name)]
|
||||||
|
mdSelectionFields = definitionSelections <=< mdDefinitions -- Workaround for safety, just take 1.
|
||||||
|
where
|
||||||
|
-- FIXME: There should only be one definition associated. Find a way to signal an error here otherwise.
|
||||||
|
mdDefinitions :: EndpointMetadata GQLQueryWithText -> [G.ExecutableDefinition G.Name]
|
||||||
|
mdDefinitions = G.getExecutableDefinitions . unGQLQuery . getGQLQuery . _edQuery . _ceDefinition
|
||||||
|
|
||||||
|
-- definitionSelections :: G.ExecutableDefinition x -> G.SelectionSet G.FragmentSpread x
|
||||||
|
definitionSelections :: G.ExecutableDefinition G.Name -> [(G.OperationType, G.Selection G.FragmentSpread G.Name)]
|
||||||
|
definitionSelections (G.ExecutableDefinitionOperation (G.OperationDefinitionTyped td)) = (G._todType td,) <$> G._todSelectionSet td
|
||||||
|
definitionSelections _ = []
|
||||||
|
|
||||||
|
mkResponse :: [Text] -> String -> Maybe RemoteSchemaIntrospection -> EndpointMetadata GQLQueryWithText -> Declare (Definitions Schema) (Maybe Response)
|
||||||
|
mkResponse _ _ Nothing _ = pure Nothing
|
||||||
|
mkResponse epMethods epUrl (Just rs) md = do
|
||||||
|
fs <- getSelectionSchema rs (mdSelectionFields md)
|
||||||
|
pure $
|
||||||
|
Just $
|
||||||
|
mempty
|
||||||
|
& content .~ OMap.singleton ("application" // "json") (mempty & schema ?~ Inline fs)
|
||||||
|
& description .~ "Responses for " <> commaSeparated epMethods <> " " <> T.pack epUrl
|
||||||
|
|
||||||
|
getSelectionSchema :: RemoteSchemaIntrospection -> [(G.OperationType, G.Selection a b)] -> Declare (Definitions Schema) Schema
|
||||||
|
getSelectionSchema rs ss = do
|
||||||
|
let fields = mapMaybe (\(o, s) -> (o,) <$> field s) ss
|
||||||
|
ps <- traverse (pure . G.unName . G._fName . snd &&&& lookupRoot rs) fields
|
||||||
|
pure $ mempty & properties .~ OMap.fromList ps
|
||||||
|
|
||||||
|
lookupRoot :: RemoteSchemaIntrospection -> (G.OperationType, G.Field a b) -> Declare (Definitions Schema) (Referenced Schema)
|
||||||
|
lookupRoot rs (ot, f) = do
|
||||||
|
let rootFieldName = getRootFieldNameFromOpType ot
|
||||||
|
fieldName = G._fName f
|
||||||
|
fieldTypeM = do
|
||||||
|
operationDefinitionSum <- lookupRS rs rootFieldName
|
||||||
|
operationDefinitionObject <- asObjectTypeDefinition operationDefinitionSum
|
||||||
|
fieldDefinition <- find ((== fieldName) . G._fldName) $ G._otdFieldsDefinition operationDefinitionObject
|
||||||
|
pure $ G._fldType fieldDefinition
|
||||||
|
|
||||||
|
case fieldTypeM of
|
||||||
|
Nothing -> pure $ Inline $ mempty & description ?~ "Couldn't find field " <> G.unName fieldName <> " in root field " <> G.unName rootFieldName
|
||||||
|
Just fieldType -> lookupDefinition rs fieldType f
|
||||||
|
|
||||||
|
lookupDefinition :: RemoteSchemaIntrospection -> G.GType -> G.Field a b -> Declare (Definitions Schema) (Referenced Schema)
|
||||||
|
lookupDefinition rs t f = do
|
||||||
|
result <- typeToSchemaM t \n -> do
|
||||||
|
case lookupRS rs n of
|
||||||
|
Nothing -> pure $ mempty & description ?~ "Couldn't find definition for type " <> G.unName n <> " in field " <> G.unName (G._fName f)
|
||||||
|
Just tDef -> getDefinitionSchema rs n tDef (G._fSelectionSet f)
|
||||||
|
|
||||||
|
pure $ Inline result
|
||||||
|
|
||||||
|
-- | A helper function to set the pattern field in Schema
|
||||||
|
-- Why not lens `pattern`? hlint doesn't like the name `pattern`
|
||||||
|
-- https://github.com/ndmitchell/hlint/issues/607
|
||||||
|
setPattern :: Maybe Pattern -> Schema -> Schema
|
||||||
|
setPattern p s = s {_schemaPattern = p}
|
||||||
|
|
||||||
|
getDefinitionSchema ::
|
||||||
|
RemoteSchemaIntrospection ->
|
||||||
|
G.Name ->
|
||||||
|
G.TypeDefinition [G.Name] RemoteSchemaInputValueDefinition ->
|
||||||
|
[G.Selection frag0 var0] ->
|
||||||
|
Declare (Definitions Schema) Schema
|
||||||
|
getDefinitionSchema rs tn td sels =
|
||||||
|
case td of
|
||||||
|
(G.TypeDefinitionInterface _) -> pure $ mempty & description ?~ "Unsupported field type TypeDefinitionInterface: " <> G.unName tn
|
||||||
|
(G.TypeDefinitionUnion _) -> pure $ mempty & description ?~ "Unsupported field type TypeDefinitionUnion: " <> G.unName tn
|
||||||
|
(G.TypeDefinitionEnum _) -> pure $ mempty & description ?~ "Unsupported field type TypeDefinitionEnum: " <> G.unName tn
|
||||||
|
(G.TypeDefinitionInputObject _) -> pure $ mempty & description ?~ "Unsupported field type TypeDefinitionInputObject: " <> G.unName tn
|
||||||
|
(G.TypeDefinitionObject otd) -> do
|
||||||
|
ps <- traverse (\sel -> getDefinitionSchemaObject rs (lookupFieldBySelection sel (G._otdFieldsDefinition otd)) sel) sels
|
||||||
|
pure $
|
||||||
|
mempty
|
||||||
|
& properties .~ OMap.fromList (catMaybes ps)
|
||||||
|
& type_ ?~ OpenApiObject
|
||||||
|
(G.TypeDefinitionScalar std) -> do
|
||||||
|
let (refType, patt) = referenceType True (T.toLower $ G.unName $ G._stdName std)
|
||||||
|
pure $
|
||||||
|
mempty
|
||||||
|
& title ?~ G.unName (G._stdName std)
|
||||||
|
& description .~ (G.unDescription <$> G._stdDescription std)
|
||||||
|
& type_ .~ refType
|
||||||
|
& setPattern patt
|
||||||
|
|
||||||
|
lookupFieldBySelection :: G.Selection frag0 var0 -> [G.FieldDefinition RemoteSchemaInputValueDefinition] -> Maybe (G.FieldDefinition RemoteSchemaInputValueDefinition)
|
||||||
|
lookupFieldBySelection (G.SelectionField f) = find \d -> G._fName f == G._fldName d
|
||||||
|
lookupFieldBySelection _ = const Nothing
|
||||||
|
|
||||||
|
getDefinitionSchemaObject ::
|
||||||
|
RemoteSchemaIntrospection ->
|
||||||
|
Maybe (G.FieldDefinition RemoteSchemaInputValueDefinition) ->
|
||||||
|
G.Selection frag0 var0 ->
|
||||||
|
Declare (Definitions Schema) (Maybe (Text, Referenced Schema))
|
||||||
|
getDefinitionSchemaObject _ Nothing _ = pure Nothing
|
||||||
|
getDefinitionSchemaObject _ _ (G.SelectionFragmentSpread _) = pure Nothing
|
||||||
|
getDefinitionSchemaObject _ _ (G.SelectionInlineFragment _) = pure Nothing
|
||||||
|
getDefinitionSchemaObject rs (Just fd) (G.SelectionField sel) = do
|
||||||
|
let sn = G._fName sel
|
||||||
|
ft = G._fldType fd
|
||||||
|
|
||||||
|
result <- typeToSchemaM ft \n -> do
|
||||||
|
let fn = G._fldName fd
|
||||||
|
case lookupRS rs n of
|
||||||
|
Nothing -> pure $ mempty & description ?~ "Couldn't find definition for type " <> G.unName n <> " in field " <> G.unName fn <> " selected by " <> G.unName sn
|
||||||
|
Just tDef -> getDefinitionSchema rs n tDef (G._fSelectionSet sel)
|
||||||
|
|
||||||
|
pure $ Just (G.unName sn, Inline result)
|
||||||
|
|
||||||
|
typeToSchemaM :: Monad m => G.GType -> (G.Name -> m Schema) -> m Schema
|
||||||
|
typeToSchemaM (G.TypeNamed _nullability tName) k = k tName
|
||||||
|
typeToSchemaM (G.TypeList n t) k = do
|
||||||
|
t' <- typeToSchemaM t k
|
||||||
|
pure $
|
||||||
|
mempty
|
||||||
|
& nullable ?~ G.unNullability n
|
||||||
|
& type_ ?~ OpenApiArray
|
||||||
|
& items ?~ OpenApiItemsObject (Inline t') -- TODO: Why do we assume objects here?
|
||||||
|
|
||||||
|
lookupRS :: RemoteSchemaIntrospection -> G.Name -> Maybe (G.TypeDefinition [G.Name] RemoteSchemaInputValueDefinition)
|
||||||
|
lookupRS (RemoteSchemaIntrospection rsDefs) n = Map.lookup n rsDefs
|
||||||
|
|
||||||
|
field :: G.Selection frag var -> Maybe (G.Field frag var)
|
||||||
|
field (G.SelectionField f) = Just f
|
||||||
|
field _ = Nothing
|
||||||
|
|
||||||
|
infixl 7 &&&&
|
||||||
|
|
||||||
|
(&&&&) :: Applicative f => (t -> f a1) -> (t -> f a2) -> t -> f (a1, a2)
|
||||||
|
f &&&& g = \a -> (,) <$> f a <*> g a
|
||||||
|
|
||||||
|
-- TODO: Move these literals somewhere else
|
||||||
|
getRootFieldNameFromOpType :: G.OperationType -> G.Name
|
||||||
|
getRootFieldNameFromOpType G.OperationTypeQuery = $$(G.litName "query_root")
|
||||||
|
getRootFieldNameFromOpType G.OperationTypeMutation = $$(G.litName "mutation_root")
|
||||||
|
getRootFieldNameFromOpType G.OperationTypeSubscription = $$(G.litName "subscription_root")
|
||||||
|
|
||||||
|
asObjectTypeDefinition :: G.TypeDefinition possibleTypes inputType -> Maybe (G.ObjectTypeDefinition inputType)
|
||||||
|
asObjectTypeDefinition (G.TypeDefinitionObject o) = Just o
|
||||||
|
asObjectTypeDefinition _ = Nothing
|
||||||
|
|
||||||
|
-- * URL / Query Params and Request Body Functions
|
||||||
|
|
||||||
getVarList :: EndpointMetadata GQLQueryWithText -> [G.VariableDefinition]
|
getVarList :: EndpointMetadata GQLQueryWithText -> [G.VariableDefinition]
|
||||||
getVarList e = vars =<< varLists
|
getVarList e = vars =<< varLists
|
||||||
@ -34,35 +280,186 @@ getVarList e = vars =<< varLists
|
|||||||
G.ExecutableDefinitionOperation (G.OperationDefinitionTyped (G.TypedOperationDefinition _ _ vds _ _)) -> vds
|
G.ExecutableDefinitionOperation (G.OperationDefinitionTyped (G.TypedOperationDefinition _ _ vds _ _)) -> vds
|
||||||
_ -> []
|
_ -> []
|
||||||
|
|
||||||
getVariableDefinitions :: EndpointMetadata GQLQueryWithText -> [Referenced Param]
|
-- There could be an additional partitioning scheme besides referentiality to support more types in Params
|
||||||
getVariableDefinitions d = fmap varDetails varList
|
getParams :: EndpointMetadata GQLQueryWithText -> [Referenced Param]
|
||||||
|
getParams d = varDetails =<< getVarList d
|
||||||
where
|
where
|
||||||
pathVars = map T.tail $ lefts $ splitPath Left Right (_ceUrl d) -- NOTE: URL Variable name ':' prefix is removed for `elem` lookup.
|
pathVars = map T.tail $ concat $ splitPath pure (const []) (_ceUrl d) -- NOTE: URL Variable name ':' prefix is removed for `elem` lookup.
|
||||||
varList = getVarList d
|
varDetails G.VariableDefinition {..} =
|
||||||
varDetails a =
|
let vName = G.unName _vdName
|
||||||
let vName = (G.unName . G._vdName $ a)
|
isRequired = not $ G.isNullable _vdType
|
||||||
in Inline $
|
in case getType _vdType of
|
||||||
mkParam
|
Left _foo -> [] -- Complex types are not allowed as params
|
||||||
vName
|
Right (vdType, patt) ->
|
||||||
Nothing
|
pure $
|
||||||
Nothing
|
Inline $
|
||||||
(if vName `elem` pathVars then ParamPath else ParamQuery)
|
mkParam
|
||||||
Nothing
|
vName
|
||||||
(getDefaultVar a)
|
(if isRequired then Just $ "_\"" <> vName <> "\" is required (enter it either in parameters or request body)_" else Nothing)
|
||||||
( case G._vdType a of
|
Nothing
|
||||||
G.TypeNamed _ na -> case G.unName na of
|
(if vName `elem` pathVars then ParamPath else ParamQuery)
|
||||||
"Int" -> Just OpenApiInteger
|
Nothing
|
||||||
"String" -> Just OpenApiString
|
(gqlToJsonValue <$> _vdDefaultValue)
|
||||||
"json" -> Just OpenApiObject
|
(Just vdType)
|
||||||
_ -> Nothing
|
patt
|
||||||
G.TypeList _ _ -> Nothing
|
|
||||||
)
|
getType :: G.GType -> Either G.GType (OpenApiType, Maybe Pattern)
|
||||||
|
getType gt@(G.TypeNamed _ na) = case referenceType True t of
|
||||||
|
(Nothing, _) -> Left gt
|
||||||
|
(Just typ, patt) -> Right (typ, patt)
|
||||||
|
where
|
||||||
|
t = T.toLower $ G.unName na
|
||||||
|
getType t = Left t -- Non scalar types are deferred to reference types for processing using introspection
|
||||||
|
|
||||||
|
mkProperties :: Maybe RemoteSchemaIntrospection -> [G.VariableDefinition] -> Declare (Definitions Schema) (InsOrdHashMap Text (Referenced Schema))
|
||||||
|
mkProperties sd ds = OMap.fromList <$> traverse (mkProperty sdMap) ds
|
||||||
|
where
|
||||||
|
sdMap = case sd of
|
||||||
|
Nothing -> OMap.empty
|
||||||
|
Just (RemoteSchemaIntrospection sd') -> OMap.fromList $ map (first G.unName) $ Map.toList sd'
|
||||||
|
|
||||||
|
mkProperty :: OMap.InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> G.VariableDefinition -> Declare (Definitions Schema) (Text, Referenced Schema)
|
||||||
|
mkProperty sd G.VariableDefinition {..} = do
|
||||||
|
d <- case getType _vdType of
|
||||||
|
Left t -> handleRefType sd t
|
||||||
|
Right (vdType, patt) ->
|
||||||
|
pure $
|
||||||
|
Inline $
|
||||||
|
mempty
|
||||||
|
& nullable ?~ G.isNullable _vdType
|
||||||
|
& type_ ?~ vdType
|
||||||
|
& default_ .~ fmap gqlToJsonValue _vdDefaultValue
|
||||||
|
& setPattern patt
|
||||||
|
|
||||||
|
pure (G.unName _vdName, d)
|
||||||
|
|
||||||
|
handleRefType :: OMap.InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> G.GType -> Declare (Definitions Schema) (Referenced Schema)
|
||||||
|
handleRefType sd = \case
|
||||||
|
G.TypeNamed nullability nameWrapper -> do
|
||||||
|
let n = G.unName nameWrapper
|
||||||
|
declareReference nullability n sd
|
||||||
|
pure $ Ref $ Reference n
|
||||||
|
G.TypeList nullability subType -> do
|
||||||
|
st <- handleRefType sd subType
|
||||||
|
pure $
|
||||||
|
Inline $
|
||||||
|
mempty
|
||||||
|
& nullable ?~ (G.unNullability nullability && G.isNullable subType)
|
||||||
|
& type_ ?~ OpenApiArray
|
||||||
|
& items ?~ OpenApiItemsObject st
|
||||||
|
|
||||||
|
-- TODO: No reference types should be nullable and only references to reference types
|
||||||
|
--
|
||||||
|
declareReference :: G.Nullability -> Text -> OMap.InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> Declare (Definitions Schema) ()
|
||||||
|
declareReference nullability n ts = do
|
||||||
|
isAvailable <- referenceAvailable n
|
||||||
|
unless isAvailable do
|
||||||
|
for_ (OMap.lookup n ts) \t -> do
|
||||||
|
let properties' = getPropertyReferences ts (typeProperties t)
|
||||||
|
|
||||||
|
result <-
|
||||||
|
declare $
|
||||||
|
OMap.singleton n $
|
||||||
|
let (refType, patt) = referenceType (null properties') (T.toLower n)
|
||||||
|
in mempty
|
||||||
|
& nullable ?~ G.unNullability nullability
|
||||||
|
& description .~ typeDescription t
|
||||||
|
& properties .~ properties'
|
||||||
|
& type_ .~ refType
|
||||||
|
& setPattern patt
|
||||||
|
void $ processProperties ts (typeProperties t)
|
||||||
|
pure result
|
||||||
|
|
||||||
|
referenceAvailable :: Text -> DeclareT (Definitions Schema) Identity Bool
|
||||||
|
referenceAvailable n = OMap.member n <$> look
|
||||||
|
|
||||||
|
getPropertyReferences :: InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> Maybe [RemoteSchemaInputValueDefinition] -> InsOrdHashMap Text (Referenced Schema)
|
||||||
|
getPropertyReferences _ Nothing = mempty
|
||||||
|
getPropertyReferences sd (Just ds) =
|
||||||
|
let ds' = fmap (processProperty' sd) ds
|
||||||
|
in OMap.fromList ds'
|
||||||
|
|
||||||
|
processProperty' :: InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> RemoteSchemaInputValueDefinition -> (Text, Referenced Schema)
|
||||||
|
processProperty' sd (RemoteSchemaInputValueDefinition d _preset) =
|
||||||
|
let n = G._ivdName d
|
||||||
|
t = G._ivdType d
|
||||||
|
rt = handleRefType' sd t
|
||||||
|
in (G.unName n, rt)
|
||||||
|
|
||||||
|
handleRefType' :: OMap.InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> G.GType -> Referenced Schema
|
||||||
|
handleRefType' sd = \case
|
||||||
|
G.TypeNamed _nullability nameWrapper ->
|
||||||
|
let n = G.unName nameWrapper
|
||||||
|
in Ref $ Reference n
|
||||||
|
G.TypeList nullability subType ->
|
||||||
|
let st = handleRefType' sd subType
|
||||||
|
in Inline $
|
||||||
|
mempty
|
||||||
|
& nullable ?~ (G.unNullability nullability && G.isNullable subType)
|
||||||
|
& type_ ?~ OpenApiArray
|
||||||
|
& items ?~ OpenApiItemsObject st
|
||||||
|
|
||||||
|
typeDescription :: G.TypeDefinition possibleTypes inputType -> Maybe Text
|
||||||
|
typeDescription = \case
|
||||||
|
(G.TypeDefinitionScalar o) -> G.unDescription <$> G._stdDescription o
|
||||||
|
(G.TypeDefinitionObject o) -> G.unDescription <$> G._otdDescription o
|
||||||
|
(G.TypeDefinitionInterface o) -> G.unDescription <$> G._itdDescription o
|
||||||
|
(G.TypeDefinitionUnion o) -> G.unDescription <$> G._utdDescription o
|
||||||
|
(G.TypeDefinitionEnum o) -> G.unDescription <$> G._etdDescription o
|
||||||
|
(G.TypeDefinitionInputObject o) -> G.unDescription <$> G._iotdDescription o
|
||||||
|
|
||||||
|
typeName :: G.TypeDefinition possibleTypes inputType -> Text
|
||||||
|
typeName = \case
|
||||||
|
(G.TypeDefinitionScalar o) -> G.unName (G._stdName o)
|
||||||
|
(G.TypeDefinitionObject o) -> G.unName (G._otdName o)
|
||||||
|
(G.TypeDefinitionInterface o) -> G.unName (G._itdName o)
|
||||||
|
(G.TypeDefinitionUnion o) -> G.unName (G._utdName o)
|
||||||
|
(G.TypeDefinitionEnum o) -> G.unName (G._etdName o)
|
||||||
|
(G.TypeDefinitionInputObject o) -> G.unName (G._iotdName o)
|
||||||
|
|
||||||
|
typeProperties :: G.TypeDefinition possibleTypes RemoteSchemaInputValueDefinition -> Maybe [RemoteSchemaInputValueDefinition]
|
||||||
|
typeProperties = \case
|
||||||
|
(G.TypeDefinitionScalar _) -> Nothing
|
||||||
|
(G.TypeDefinitionInterface _) -> Nothing
|
||||||
|
(G.TypeDefinitionUnion _) -> Nothing
|
||||||
|
(G.TypeDefinitionEnum _) -> Nothing
|
||||||
|
(G.TypeDefinitionInputObject o) -> Just $ G._iotdValueDefinitions o
|
||||||
|
(G.TypeDefinitionObject _) -> Nothing
|
||||||
|
|
||||||
|
-- TODO: Can we reuse something from rest module to handle this?
|
||||||
|
-- TODO: referenceType could be improved, instead of using Bool (to indicate if it is object or scalar),
|
||||||
|
-- we can do something better
|
||||||
|
referenceType :: Bool -> Text -> (Maybe OpenApiType, Maybe Pattern)
|
||||||
|
referenceType False = const (Just OpenApiObject, Nothing)
|
||||||
|
referenceType True = \case
|
||||||
|
"int" -> (Just OpenApiInteger, Nothing)
|
||||||
|
"float" -> (Just OpenApiNumber, Nothing)
|
||||||
|
"double" -> (Just OpenApiNumber, Nothing)
|
||||||
|
"uuid" -> (Just OpenApiString, Just "[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}")
|
||||||
|
"bool" -> (Just OpenApiBoolean, Nothing)
|
||||||
|
"boolean" -> (Just OpenApiBoolean, Nothing)
|
||||||
|
"string" -> (Just OpenApiString, Nothing)
|
||||||
|
"id" -> (Just OpenApiString, Nothing)
|
||||||
|
_ -> (Nothing, Nothing)
|
||||||
|
|
||||||
|
processProperties :: InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> Maybe [RemoteSchemaInputValueDefinition] -> DeclareT (Definitions Schema) Identity (InsOrdHashMap Text (Referenced Schema))
|
||||||
|
processProperties _ Nothing = pure mempty
|
||||||
|
processProperties sd (Just ds) = do
|
||||||
|
ds' <- traverse (processProperty sd) ds
|
||||||
|
pure $ OMap.fromList ds'
|
||||||
|
|
||||||
|
processProperty :: InsOrdHashMap Text (G.TypeDefinition a RemoteSchemaInputValueDefinition) -> RemoteSchemaInputValueDefinition -> DeclareT (Definitions Schema) Identity (Text, Referenced Schema)
|
||||||
|
processProperty sd (RemoteSchemaInputValueDefinition d _preset) = do
|
||||||
|
let n = G._ivdName d
|
||||||
|
t = G._ivdType d
|
||||||
|
rt <- handleRefType sd t
|
||||||
|
pure (G.unName n, rt)
|
||||||
|
|
||||||
getGQLQueryFromTrie :: EndpointMetadata GQLQueryWithText -> Text
|
getGQLQueryFromTrie :: EndpointMetadata GQLQueryWithText -> Text
|
||||||
getGQLQueryFromTrie = getGQLQueryText . _edQuery . _ceDefinition
|
getGQLQueryFromTrie = getGQLQueryText . _edQuery . _ceDefinition
|
||||||
|
|
||||||
mkParam :: Text -> Maybe Text -> Maybe Bool -> ParamLocation -> Maybe Bool -> Maybe Value -> Maybe OpenApiType -> Param
|
mkParam :: Text -> Maybe Text -> Maybe Bool -> ParamLocation -> Maybe Bool -> Maybe J.Value -> Maybe OpenApiType -> Maybe Pattern -> Param
|
||||||
mkParam nameP desc req loc allowEmpty def varType =
|
mkParam nameP desc req loc allowEmpty def varType patt =
|
||||||
mempty
|
mempty
|
||||||
& name .~ nameP
|
& name .~ nameP
|
||||||
& description .~ desc
|
& description .~ desc
|
||||||
@ -74,19 +471,21 @@ mkParam nameP desc req loc allowEmpty def varType =
|
|||||||
( mempty
|
( mempty
|
||||||
& default_ .~ def
|
& default_ .~ def
|
||||||
& type_ .~ varType
|
& type_ .~ varType
|
||||||
|
& setPattern patt
|
||||||
)
|
)
|
||||||
|
|
||||||
getDefaultVar :: G.VariableDefinition -> Maybe Value
|
gqlToJsonValue :: G.Value Void -> J.Value
|
||||||
getDefaultVar var = case G._vdDefaultValue var of
|
gqlToJsonValue = \case
|
||||||
Nothing -> Nothing
|
G.VNull -> J.Null
|
||||||
Just va -> case va of
|
G.VInt n -> J.toJSON n
|
||||||
G.VNull -> Nothing
|
G.VFloat sci -> J.toJSON sci
|
||||||
G.VInt n -> Just $ toJSON n
|
G.VString txt -> J.toJSON txt
|
||||||
G.VFloat sci -> Just $ toJSON sci
|
G.VBoolean b -> J.toJSON b
|
||||||
G.VString txt -> Just $ toJSON txt
|
G.VEnum ev -> J.toJSON ev
|
||||||
G.VBoolean b -> Just $ toJSON b
|
G.VList lst -> J.toJSON $ gqlToJsonValue <$> lst
|
||||||
G.VEnum ev -> Just $ toJSON ev
|
G.VObject obj -> J.toJSON $ gqlToJsonValue <$> obj
|
||||||
_ -> Nothing
|
|
||||||
|
-- * Top level schema construction
|
||||||
|
|
||||||
getComment :: EndpointMetadata GQLQueryWithText -> Text
|
getComment :: EndpointMetadata GQLQueryWithText -> Text
|
||||||
getComment d = comment
|
getComment d = comment
|
||||||
@ -101,49 +500,110 @@ getURL d =
|
|||||||
"/api/rest/"
|
"/api/rest/"
|
||||||
-- The url will be of the format <Endpoint>/:<Var1>/:<Var2> ... always, so we can
|
-- The url will be of the format <Endpoint>/:<Var1>/:<Var2> ... always, so we can
|
||||||
-- split and take the first element (it should never fail)
|
-- split and take the first element (it should never fail)
|
||||||
<> fst (T.breakOn "/" (TNE.unNonEmptyText . unEndpointUrl . _ceUrl $ d))
|
<> fst (T.breakOn "/" (unNonEmptyText . unEndpointUrl . _ceUrl $ d))
|
||||||
<> foldl
|
<> foldl
|
||||||
( \b a -> b <> "/{" <> a <> "}"
|
( \b a -> b <> "/{" <> a <> "}"
|
||||||
)
|
)
|
||||||
""
|
""
|
||||||
(map T.tail $ lefts $ splitPath Left Right (_ceUrl d))
|
(map T.tail $ lefts $ splitPath Left Right (_ceUrl d))
|
||||||
|
|
||||||
extractEndpointInfo :: EndpointMethod -> EndpointMetadata GQLQueryWithText -> EndpointData
|
extractEndpointInfo :: Maybe RemoteSchemaIntrospection -> EndpointMethod -> EndpointMetadata GQLQueryWithText -> Declare (Definitions Schema) EndpointData
|
||||||
extractEndpointInfo method d =
|
extractEndpointInfo sd method d = do
|
||||||
let _edUrl = T.unpack . getURL $ d
|
_edProperties <- mkProperties sd (getVarList d)
|
||||||
_edVarList = getVariableDefinitions d
|
_edResponse <- mkResponse _edMethod _edUrl sd d
|
||||||
_edDescription = getComment d
|
pure EndpointData {..}
|
||||||
_edName = TNE.unNonEmptyText $ unEndpointName $ _ceName d
|
|
||||||
in EndpointData
|
|
||||||
{ _edMethod = [unEndpointMethod method], -- NOTE: Methods are grouped with into matching endpoints - Name used for grouping.
|
|
||||||
..
|
|
||||||
}
|
|
||||||
|
|
||||||
getEndpointsData :: SchemaCache -> [EndpointData]
|
|
||||||
getEndpointsData sc = map squashEndpointGroup endpointsGrouped
|
|
||||||
where
|
where
|
||||||
endpointTrie = scEndpoints sc
|
_edUrl = T.unpack . getURL $ d
|
||||||
methodMaps = leaves endpointTrie
|
_edVarList = getParams d
|
||||||
endpointsWithMethods = concatMap (\(m, s) -> map (m,) (S.toList s)) $ concatMap (M.toList . _unMultiMap) methodMaps
|
_edDescription = getComment d
|
||||||
endpointsWithInfo = map (uncurry extractEndpointInfo) endpointsWithMethods
|
_edName = unNonEmptyText $ unEndpointName $ _ceName d
|
||||||
endpointsGrouped = LNE.groupBy (\a b -> _edName a == _edName b) endpointsWithInfo
|
_edMethod = [unEndpointMethod method] -- NOTE: Methods are grouped with into matching endpoints - Name used for grouping.
|
||||||
|
|
||||||
|
getEndpointsData :: Maybe RemoteSchemaIntrospection -> SchemaCache -> Declare (Definitions Schema) [EndpointData]
|
||||||
|
getEndpointsData sd sc = do
|
||||||
|
let endpointTrie = scEndpoints sc
|
||||||
|
methodMaps = leaves endpointTrie
|
||||||
|
endpointsWithMethods = concatMap (\(m, s) -> map (m,) (Set.toList s)) $ concatMap (Map.toList . _unMultiMap) methodMaps
|
||||||
|
|
||||||
|
endpointsWithInfo <- traverse (uncurry (extractEndpointInfo sd)) endpointsWithMethods
|
||||||
|
|
||||||
|
let endpointsGrouped = NE.groupBy (\a b -> _edName a == _edName b) endpointsWithInfo
|
||||||
|
|
||||||
|
pure $ map squashEndpointGroup endpointsGrouped
|
||||||
|
|
||||||
squashEndpointGroup :: NonEmpty EndpointData -> EndpointData
|
squashEndpointGroup :: NonEmpty EndpointData -> EndpointData
|
||||||
squashEndpointGroup g = (LNE.head g) {_edMethod = concatMap _edMethod g}
|
squashEndpointGroup g = (NE.head g) {_edMethod = concatMap _edMethod g}
|
||||||
|
|
||||||
|
getSchemaIntrospection :: SchemaCache -> Maybe RemoteSchemaIntrospection
|
||||||
|
getSchemaIntrospection SchemaCache {..} = do
|
||||||
|
RoleContext {..} <- Map.lookup adminRoleName scGQLContext
|
||||||
|
fieldMap <- either (const Nothing) Just $ gqlQueryParser _rctxDefault $ fmap (fmap nameToVariable) $ G._todSelectionSet $ _grQuery introspectionQuery
|
||||||
|
RFRaw v <- OMap.lookup (mkUnNamespacedRootFieldAlias $$(G.litName "__schema")) fieldMap
|
||||||
|
fmap irDoc $ parseIntrospectionResult $ J.object [("data", J.object [("__schema", JO.fromOrdered v)])]
|
||||||
|
where
|
||||||
|
-- This value isn't used but we give it a type to be more clear about what is being ignored
|
||||||
|
nameToVariable :: G.Name -> Variable
|
||||||
|
nameToVariable = undefined
|
||||||
|
|
||||||
serveJSON :: SchemaCache -> OpenApi
|
serveJSON :: SchemaCache -> OpenApi
|
||||||
serveJSON sc = spec & components . schemas .~ defs
|
serveJSON sc = spec & components . schemas .~ defs
|
||||||
where
|
where
|
||||||
(defs, spec) = runDeclare (declareOpenApiSpec sc) mempty
|
(defs, spec) = runDeclare (declareOpenApiSpec sc) mempty
|
||||||
|
|
||||||
|
-- | If all variables are scalar or optional then the entire request body can be marked as optional
|
||||||
|
isRequestBodyRequired :: EndpointData -> Bool
|
||||||
|
isRequestBodyRequired ed = not $ all isNotRequired (_edProperties ed)
|
||||||
|
where
|
||||||
|
-- The use of isNotRequired here won't work with list types since they are inline, but contain references
|
||||||
|
isNotRequired (Inline Schema {..}) = isScalarType _schemaType || (Just True == _schemaNullable)
|
||||||
|
isNotRequired (Ref _) = False -- Not all `Ref` are non nullable, imagine two endpoints using the same Ref one being nullable and other not
|
||||||
|
isScalarType :: Maybe OpenApiType -> Bool
|
||||||
|
isScalarType Nothing = False
|
||||||
|
isScalarType (Just t) = case t of
|
||||||
|
OpenApiString -> True
|
||||||
|
OpenApiNumber -> True
|
||||||
|
OpenApiInteger -> True
|
||||||
|
OpenApiBoolean -> True
|
||||||
|
OpenApiArray -> False
|
||||||
|
OpenApiNull -> False
|
||||||
|
OpenApiObject -> False
|
||||||
|
|
||||||
|
-- * Entry point
|
||||||
|
|
||||||
declareOpenApiSpec :: SchemaCache -> Declare (Definitions Schema) OpenApi
|
declareOpenApiSpec :: SchemaCache -> Declare (Definitions Schema) OpenApi
|
||||||
declareOpenApiSpec sc = do
|
declareOpenApiSpec sc = do
|
||||||
let mkOperation :: EndpointData -> Operation
|
let _schemaIntrospection = getSchemaIntrospection sc
|
||||||
|
warnings = case _schemaIntrospection of
|
||||||
|
Nothing -> "\n\n⚠️ Schema introspection failed"
|
||||||
|
_ -> ""
|
||||||
|
|
||||||
|
mkRequestBody :: EndpointData -> RequestBody
|
||||||
|
mkRequestBody ed =
|
||||||
|
mempty
|
||||||
|
& description ?~ "Query parameters can also be provided in the request body as a JSON object"
|
||||||
|
& required ?~ isRequestBodyRequired ed
|
||||||
|
& content
|
||||||
|
.~ OMap.singleton
|
||||||
|
("application" // "json")
|
||||||
|
( mempty
|
||||||
|
& schema
|
||||||
|
?~ Inline
|
||||||
|
( mempty
|
||||||
|
& type_ ?~ OpenApiObject
|
||||||
|
& properties .~ _edProperties ed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mkOperation :: EndpointData -> Operation
|
||||||
mkOperation ed =
|
mkOperation ed =
|
||||||
mempty
|
mempty
|
||||||
& description ?~ _edDescription ed
|
& description ?~ _edDescription ed
|
||||||
& summary ?~ _edName ed
|
& summary ?~ _edName ed
|
||||||
& parameters .~ (Inline xHasuraAS : _edVarList ed)
|
& parameters .~ (Inline xHasuraAdminSecret : _edVarList ed)
|
||||||
|
& requestBody .~ toMaybe (not (null (_edProperties ed))) (Inline (mkRequestBody ed))
|
||||||
|
& responses .~ Responses Nothing (maybe mempty (OMap.singleton 200 . Inline) (_edResponse ed))
|
||||||
|
where
|
||||||
|
toMaybe b a = if b then Just a else Nothing
|
||||||
|
|
||||||
getOPName :: EndpointData -> Text -> Maybe Operation
|
getOPName :: EndpointData -> Text -> Maybe Operation
|
||||||
getOPName ed methodType =
|
getOPName ed methodType =
|
||||||
@ -151,8 +611,8 @@ declareOpenApiSpec sc = do
|
|||||||
then Just $ mkOperation ed
|
then Just $ mkOperation ed
|
||||||
else Nothing
|
else Nothing
|
||||||
|
|
||||||
xHasuraAS :: Param
|
xHasuraAdminSecret :: Param
|
||||||
xHasuraAS =
|
xHasuraAdminSecret =
|
||||||
mkParam
|
mkParam
|
||||||
"x-hasura-admin-secret"
|
"x-hasura-admin-secret"
|
||||||
(Just "Your x-hasura-admin-secret will be used for authentication of the API request.")
|
(Just "Your x-hasura-admin-secret will be used for authentication of the API request.")
|
||||||
@ -161,20 +621,37 @@ declareOpenApiSpec sc = do
|
|||||||
Nothing
|
Nothing
|
||||||
Nothing
|
Nothing
|
||||||
(Just OpenApiString)
|
(Just OpenApiString)
|
||||||
|
Nothing
|
||||||
|
|
||||||
generatePathItem :: EndpointData -> PathItem
|
generatePathItem :: EndpointData -> PathItem
|
||||||
generatePathItem ed =
|
generatePathItem ed =
|
||||||
mempty
|
let pathData =
|
||||||
& get .~ getOPName ed "GET"
|
mempty
|
||||||
& post .~ getOPName ed "POST"
|
& get .~ getOPName ed "GET"
|
||||||
& put .~ getOPName ed "PUT"
|
& post .~ getOPName ed "POST"
|
||||||
& delete .~ getOPName ed "DELETE"
|
& put .~ getOPName ed "PUT"
|
||||||
& patch .~ getOPName ed "PATCH"
|
& delete .~ getOPName ed "DELETE"
|
||||||
|
& patch .~ getOPName ed "PATCH"
|
||||||
|
completePathData =
|
||||||
|
if pathData == mempty
|
||||||
|
then
|
||||||
|
mempty
|
||||||
|
& post
|
||||||
|
?~ mkOperation
|
||||||
|
ed
|
||||||
|
{ _edDescription =
|
||||||
|
"⚠️ Method("
|
||||||
|
<> tshow (_edMethod ed)
|
||||||
|
<> ") not supported, defaulting to POST\n\n"
|
||||||
|
<> _edDescription ed
|
||||||
|
}
|
||||||
|
else pathData
|
||||||
|
in completePathData
|
||||||
|
|
||||||
endpointLst = getEndpointsData sc
|
endpointLst <- getEndpointsData _schemaIntrospection sc
|
||||||
|
|
||||||
mkOpenAPISchema :: [EndpointData] -> InsOrdHashMap FilePath PathItem
|
let mkOpenAPISchema :: [EndpointData] -> InsOrdHashMap FilePath PathItem
|
||||||
mkOpenAPISchema edLst = foldl (\hm ed -> MI.insertWith (<>) (_edUrl ed) (generatePathItem ed) hm) mempty edLst
|
mkOpenAPISchema edLst = foldl (\hm ed -> OMap.insertWith (<>) (_edUrl ed) (generatePathItem ed) hm) mempty edLst
|
||||||
|
|
||||||
openAPIPaths = mkOpenAPISchema endpointLst
|
openAPIPaths = mkOpenAPISchema endpointLst
|
||||||
|
|
||||||
@ -182,4 +659,4 @@ declareOpenApiSpec sc = do
|
|||||||
mempty
|
mempty
|
||||||
& paths .~ openAPIPaths
|
& paths .~ openAPIPaths
|
||||||
& info . title .~ "Rest Endpoints"
|
& info . title .~ "Rest Endpoints"
|
||||||
& info . description ?~ "These OpenAPI specifications are automatically generated by Hasura."
|
& info . description ?~ "This OpenAPI specification is automatically generated by Hasura." <> warnings
|
||||||
|
@ -8,5 +8,5 @@ response:
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
components: {}
|
components: {}
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
url: multi_method_endpoint
|
url: multi_method_endpoint
|
||||||
name: multi_method_endpoint
|
name: multi_method_endpoint
|
||||||
methods:
|
methods:
|
||||||
- GET
|
- GET
|
||||||
- POST
|
- POST
|
||||||
- PUT
|
- PUT
|
||||||
- DELETE
|
- DELETE
|
||||||
- PATCH
|
- PATCH
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -30,166 +30,315 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/multi_method_endpoint:
|
/api/rest/multi_method_endpoint:
|
||||||
get:
|
get:
|
||||||
summary: multi_method_endpoint
|
summary: multi_method_endpoint
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/multi_method_endpoint
|
||||||
put:
|
put:
|
||||||
summary: multi_method_endpoint
|
summary: multi_method_endpoint
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/multi_method_endpoint
|
||||||
post:
|
post:
|
||||||
summary: multi_method_endpoint
|
summary: multi_method_endpoint
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/multi_method_endpoint
|
||||||
delete:
|
delete:
|
||||||
summary: multi_method_endpoint
|
summary: multi_method_endpoint
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/multi_method_endpoint
|
||||||
patch:
|
patch:
|
||||||
summary: multi_method_endpoint
|
summary: multi_method_endpoint
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/multi_method_endpoint
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
- description: Try to add a GET rest endpoint with no argument
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: complex_arg
|
||||||
|
name: complex_arg
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: mutation_complex_arg
|
||||||
|
|
||||||
|
|
||||||
|
- description: Call openapi json endpoint
|
||||||
|
url: /api/swagger/json
|
||||||
|
method: GET
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
response:
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: ''
|
||||||
|
title: Rest Endpoints
|
||||||
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/complex_arg:
|
||||||
|
post:
|
||||||
|
summary: complex_arg
|
||||||
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
|
mutation ($objects: [test_table_insert_input!]!) { insert_test_table( objects:\
|
||||||
|
\ $objects) { returning { id } } }\n```"
|
||||||
|
parameters:
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: header
|
||||||
|
name: x-hasura-admin-secret
|
||||||
|
description: Your x-hasura-admin-secret will be used for authentication of
|
||||||
|
the API request.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
objects:
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/test_table_insert_input"
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body as
|
||||||
|
a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/complex_arg
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
String:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
uuid:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
test_table_insert_input:
|
||||||
|
type: object
|
||||||
|
description: input type for inserting data into table "test_table"
|
||||||
|
nullable: false
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
"$ref": "#/components/schemas/String"
|
||||||
|
last_name:
|
||||||
|
"$ref": "#/components/schemas/String"
|
||||||
|
id:
|
||||||
|
"$ref": "#/components/schemas/uuid"
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: complex_arg
|
@ -0,0 +1,105 @@
|
|||||||
|
- description: Try to add a GET rest endpoint with no argument
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: complex_args
|
||||||
|
name: complex_args
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: mutation_complex_args
|
||||||
|
|
||||||
|
- description: Call openapi json endpoint
|
||||||
|
url: /api/swagger/json
|
||||||
|
method: GET
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
response:
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: ''
|
||||||
|
title: Rest Endpoints
|
||||||
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/complex_args:
|
||||||
|
post:
|
||||||
|
summary: complex_args
|
||||||
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
|
mutation QQ($new_object: test_table_set_input!, $first_name: String!) {\
|
||||||
|
\ update_test_table(where: {first_name: {_eq: $first_name}}, _set: $new_object)\
|
||||||
|
\ { affected_rows } }\n```"
|
||||||
|
parameters:
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: header
|
||||||
|
name: x-hasura-admin-secret
|
||||||
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
|
of the API request.
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: first_name
|
||||||
|
description: _"first_name" is required (enter it either in parameters or
|
||||||
|
request body)_
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
new_object:
|
||||||
|
$ref: '#/components/schemas/test_table_set_input'
|
||||||
|
description: Query parameters can also be provided in the request body as
|
||||||
|
a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
update_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/complex_args
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
String:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
uuid:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
test_table_set_input:
|
||||||
|
type: object
|
||||||
|
description: input type for updating data in table "test_table"
|
||||||
|
nullable: false
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
$ref: '#/components/schemas/String'
|
||||||
|
last_name:
|
||||||
|
$ref: '#/components/schemas/String'
|
||||||
|
id:
|
||||||
|
$ref: '#/components/schemas/uuid'
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: complex_args
|
@ -9,7 +9,7 @@
|
|||||||
url: simple
|
url: simple
|
||||||
name: simple
|
name: simple
|
||||||
methods:
|
methods:
|
||||||
- GET
|
- GET
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,28 +26,40 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/simple:
|
/api/rest/simple:
|
||||||
get:
|
get:
|
||||||
summary: simple
|
summary: simple
|
||||||
description: |-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query { test_table { first_name last_name } }\n```"
|
||||||
The GraphQl query for this endpoint is:
|
|
||||||
``` graphql
|
|
||||||
query { test_table { first_name last_name } }
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for GET /api/rest/simple
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
url: my_endpoint_url
|
url: my_endpoint_url
|
||||||
name: with_default_arg
|
name: with_default_arg
|
||||||
methods:
|
methods:
|
||||||
- GET
|
- GET
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,7 +26,7 @@
|
|||||||
url: my_endpoint_url
|
url: my_endpoint_url
|
||||||
name: mutation_with_args
|
name: mutation_with_args
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -42,69 +42,120 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/my_endpoint_url:
|
/api/rest/my_endpoint_url:
|
||||||
get:
|
get:
|
||||||
summary: with_default_arg
|
summary: with_default_arg
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name:String=\"Foo\") { test_table(where: {first_name: {\
|
||||||
|
\ _eq: $first_name } }) { first_name last_name } }\n```"
|
||||||
The GraphQl query for this endpoint is:
|
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name:String="Foo") { test_table(where: {first_name: { _eq:
|
|
||||||
$first_name } }) { first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
default: Foo
|
||||||
default: Foo
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
default: Foo
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for GET /api/rest/my_endpoint_url
|
||||||
post:
|
post:
|
||||||
summary: mutation_with_args
|
summary: mutation_with_args
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/my_endpoint_url
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
url: with_args_url/:first_name/:last_name
|
url: with_args_url/:first_name/:last_name
|
||||||
name: with_args_url
|
name: with_args_url
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,7 +26,7 @@
|
|||||||
url: with_default_arg
|
url: with_default_arg
|
||||||
name: with_default_arg
|
name: with_default_arg
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -43,7 +43,7 @@
|
|||||||
url: mutation_with_args
|
url: mutation_with_args
|
||||||
name: mutation_with_args
|
name: mutation_with_args
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -59,102 +59,179 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/mutation_with_args:
|
/api/rest/mutation_with_args:
|
||||||
post:
|
post:
|
||||||
summary: mutation_with_args
|
summary: mutation_with_args
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
mutation ($first_name: String!, $last_name: String!) { insert_test_table(\
|
||||||
|
\ objects: {first_name: $first_name, last_name: $last_name }) { returning\
|
||||||
The GraphQl query for this endpoint is:
|
\ { id } affected_rows } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
mutation ($first_name: String!, $last_name: String!) {
|
|
||||||
insert_test_table( objects: {first_name: $first_name, last_name:
|
|
||||||
$last_name }) { returning { id } affected_rows } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
returning:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
affected_rows:
|
||||||
|
title: Int
|
||||||
|
type: integer
|
||||||
|
description: Responses for POST /api/rest/mutation_with_args
|
||||||
/api/rest/with_default_arg:
|
/api/rest/with_default_arg:
|
||||||
post:
|
post:
|
||||||
summary: with_default_arg
|
summary: with_default_arg
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name:String=\"Foo\") { test_table(where: {first_name: {\
|
||||||
|
\ _eq: $first_name } }) { first_name last_name } }\n```"
|
||||||
The GraphQl query for this endpoint is:
|
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name:String="Foo") { test_table(where: {first_name: { _eq:
|
|
||||||
$first_name } }) { first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
default: Foo
|
||||||
default: Foo
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
requestBody:
|
||||||
'/api/rest/with_args_url/{first_name}/{last_name}':
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
default: Foo
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/with_default_arg
|
||||||
|
/api/rest/with_args_url/{first_name}/{last_name}:
|
||||||
post:
|
post:
|
||||||
summary: with_args_url
|
summary: with_args_url
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name: String!, $last_name:String!) { test_table(where: {first_name:\
|
||||||
|
\ { _eq: $first_name } last_name: { _eq: $last_name }}) { first_name last_name\
|
||||||
The GraphQl query for this endpoint is:
|
\ } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name: String!, $last_name:String!) { test_table(where:
|
|
||||||
{first_name: { _eq: $first_name } last_name: { _eq: $last_name }}) {
|
|
||||||
first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: path
|
||||||
in: path
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: path
|
in: path
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/with_args_url/{first_name}/{last_name}
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
- description: Try to add a POST rest endpoint with arguments
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: recurse_arg
|
||||||
|
name: recurse_arg
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: mutation_recursive_arg
|
||||||
|
|
||||||
|
|
||||||
|
- description: Call openapi json endpoint
|
||||||
|
url: /api/swagger/json
|
||||||
|
method: GET
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
response:
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: ''
|
||||||
|
title: Rest Endpoints
|
||||||
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/recurse_arg:
|
||||||
|
post:
|
||||||
|
summary: recurse_arg
|
||||||
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
|
mutation MyMutation($object: test_table_recurse_insert_input!) { insert_test_table_recurse_one(object:\
|
||||||
|
\ $object) { id } }\n```"
|
||||||
|
parameters:
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: header
|
||||||
|
name: x-hasura-admin-secret
|
||||||
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
|
of the API request.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
object:
|
||||||
|
$ref: '#/components/schemas/test_table_recurse_insert_input'
|
||||||
|
description: Query parameters can also be provided in the request body as
|
||||||
|
a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
insert_test_table_recurse_one:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
title: uuid
|
||||||
|
type: string
|
||||||
|
description: Responses for POST /api/rest/recurse_arg
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
test_table_recurse_insert_input:
|
||||||
|
type: object
|
||||||
|
description: input type for inserting data into table "test_table_recurse"
|
||||||
|
nullable: false
|
||||||
|
properties:
|
||||||
|
random_data:
|
||||||
|
$ref: '#/components/schemas/String'
|
||||||
|
id:
|
||||||
|
$ref: '#/components/schemas/uuid'
|
||||||
|
recurse_rel:
|
||||||
|
$ref: '#/components/schemas/test_table_recurse_obj_rel_insert_input'
|
||||||
|
uuid:
|
||||||
|
pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}'
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
String:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
test_table_recurse_obj_rel_insert_input:
|
||||||
|
type: object
|
||||||
|
description: input type for inserting object relation for remote table "test_table_recurse"
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/test_table_recurse_insert_input'
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: recurse_arg
|
@ -9,7 +9,7 @@
|
|||||||
url: with_args
|
url: with_args
|
||||||
name: with_args
|
name: with_args
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,42 +26,67 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/with_args:
|
/api/rest/with_args:
|
||||||
post:
|
post:
|
||||||
summary: with_args
|
summary: with_args
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name: String!, $last_name:String!) { test_table(where: {first_name:\
|
||||||
|
\ { _eq: $first_name } last_name: { _eq: $last_name }}) { first_name last_name\
|
||||||
The GraphQl query for this endpoint is:
|
\ } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name: String!, $last_name:String!) { test_table(where:
|
|
||||||
{first_name: { _eq: $first_name } last_name: { _eq: $last_name }}) {
|
|
||||||
first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/with_args
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
url: with_args_url/:first_name/:last_name
|
url: with_args_url/:first_name/:last_name
|
||||||
name: with_args_url
|
name: with_args_url
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,42 +26,67 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
'/api/rest/with_args_url/{first_name}/{last_name}':
|
/api/rest/with_args_url/{first_name}/{last_name}:
|
||||||
post:
|
post:
|
||||||
summary: with_args_url
|
summary: with_args_url
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name: String!, $last_name:String!) { test_table(where: {first_name:\
|
||||||
|
\ { _eq: $first_name } last_name: { _eq: $last_name }}) { first_name last_name\
|
||||||
The GraphQl query for this endpoint is:
|
\ } }\n```"
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name: String!, $last_name:String!) { test_table(where:
|
|
||||||
{first_name: { _eq: $first_name } last_name: { _eq: $last_name }}) {
|
|
||||||
first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
type: string
|
||||||
type: string
|
in: path
|
||||||
in: path
|
name: first_name
|
||||||
name: first_name
|
description: _"first_name" is required (enter it either in parameters or request body)_
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: path
|
in: path
|
||||||
name: last_name
|
name: last_name
|
||||||
|
description: _"last_name" is required (enter it either in parameters or request body)_
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/with_args_url/{first_name}/{last_name}
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
url: with_default_arg
|
url: with_default_arg
|
||||||
name: with_default_arg
|
name: with_default_arg
|
||||||
methods:
|
methods:
|
||||||
- POST
|
- POST
|
||||||
definition:
|
definition:
|
||||||
query:
|
query:
|
||||||
collection_name: test_collection
|
collection_name: test_collection
|
||||||
@ -26,38 +26,59 @@
|
|||||||
info:
|
info:
|
||||||
version: ''
|
version: ''
|
||||||
title: Rest Endpoints
|
title: Rest Endpoints
|
||||||
description: These OpenAPI specifications are automatically generated by Hasura.
|
description: This OpenAPI specification is automatically generated by Hasura.
|
||||||
paths:
|
paths:
|
||||||
/api/rest/with_default_arg:
|
/api/rest/with_default_arg:
|
||||||
post:
|
post:
|
||||||
summary: with_default_arg
|
summary: with_default_arg
|
||||||
description: >-
|
description: "***\nThe GraphQl query for this endpoint is:\n``` graphql\n\
|
||||||
***
|
query ($first_name:String=\"Foo\") { test_table(where: {first_name: {\
|
||||||
|
\ _eq: $first_name } }) { first_name last_name } }\n```"
|
||||||
The GraphQl query for this endpoint is:
|
|
||||||
|
|
||||||
``` graphql
|
|
||||||
|
|
||||||
query ($first_name:String="Foo") { test_table(where: {first_name: { _eq:
|
|
||||||
$first_name } }) { first_name last_name } }
|
|
||||||
|
|
||||||
```
|
|
||||||
responses: {}
|
|
||||||
parameters:
|
parameters:
|
||||||
- schema:
|
- schema:
|
||||||
type: string
|
type: string
|
||||||
in: header
|
in: header
|
||||||
name: x-hasura-admin-secret
|
name: x-hasura-admin-secret
|
||||||
description: >-
|
description: Your x-hasura-admin-secret will be used for authentication
|
||||||
Your x-hasura-admin-secret will be used for authentication of the API
|
of the API request.
|
||||||
request.
|
- schema:
|
||||||
- schema:
|
default: Foo
|
||||||
default: Foo
|
type: string
|
||||||
type: string
|
in: query
|
||||||
in: query
|
name: first_name
|
||||||
name: first_name
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
default: Foo
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Query parameters can also be provided in the request body
|
||||||
|
as a JSON object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
test_table:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
title: String
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
nullable: false
|
||||||
|
description: Responses for POST /api/rest/with_default_arg
|
||||||
components: {}
|
components: {}
|
||||||
|
|
||||||
- description: Try to remove the endpoint
|
- description: Try to remove the endpoint
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
status: 200
|
status: 200
|
||||||
|
@ -23,6 +23,29 @@ args:
|
|||||||
('Baz', 'Qux'),
|
('Baz', 'Qux'),
|
||||||
('X%20Y', 'Test');
|
('X%20Y', 'Test');
|
||||||
|
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
create table test_table_recurse(
|
||||||
|
random_data text,
|
||||||
|
id UUID NOT NULL DEFAULT gen_random_uuid()
|
||||||
|
);
|
||||||
|
|
||||||
|
- type: track_table
|
||||||
|
args:
|
||||||
|
schema: public
|
||||||
|
name: test_table_recurse
|
||||||
|
|
||||||
|
- type: create_object_relationship
|
||||||
|
args:
|
||||||
|
table: test_table_recurse
|
||||||
|
name: recurse_rel
|
||||||
|
using:
|
||||||
|
manual_configuration:
|
||||||
|
remote_table: test_table_recurse
|
||||||
|
column_mapping:
|
||||||
|
id : id
|
||||||
|
|
||||||
- type: create_query_collection
|
- type: create_query_collection
|
||||||
args:
|
args:
|
||||||
name: test_collection
|
name: test_collection
|
||||||
@ -40,3 +63,9 @@ args:
|
|||||||
query: "query ($first_name:String=\"Foo\") { test_table(where: {first_name: { _eq: $first_name } }) { first_name last_name } }"
|
query: "query ($first_name:String=\"Foo\") { test_table(where: {first_name: { _eq: $first_name } }) { first_name last_name } }"
|
||||||
- name: mutation_with_args
|
- name: mutation_with_args
|
||||||
query: "mutation ($first_name: String!, $last_name: String!) { insert_test_table( objects: {first_name: $first_name, last_name: $last_name }) { returning { id } affected_rows } }"
|
query: "mutation ($first_name: String!, $last_name: String!) { insert_test_table( objects: {first_name: $first_name, last_name: $last_name }) { returning { id } affected_rows } }"
|
||||||
|
- name: mutation_complex_arg
|
||||||
|
query: "mutation ($objects: [test_table_insert_input!]!) { insert_test_table( objects: $objects) { returning { id } } }"
|
||||||
|
- name: mutation_complex_args
|
||||||
|
query: "mutation QQ($new_object: test_table_set_input!, $first_name: String!) { update_test_table(where: {first_name: {_eq: $first_name}}, _set: $new_object) { affected_rows } }"
|
||||||
|
- name: mutation_recursive_arg
|
||||||
|
query: "mutation MyMutation($object: test_table_recurse_insert_input!) { insert_test_table_recurse_one(object: $object) { id } }"
|
@ -7,4 +7,4 @@ args:
|
|||||||
- type: run_sql
|
- type: run_sql
|
||||||
args:
|
args:
|
||||||
sql: |
|
sql: |
|
||||||
drop table test_table
|
drop table test_table, test_table_recurse;
|
||||||
|
@ -35,3 +35,11 @@ class TestOpenAPISpec:
|
|||||||
def test_multiple_endpoints_same_path(self, hge_ctx, transport):
|
def test_multiple_endpoints_same_path(self, hge_ctx, transport):
|
||||||
check_query_f(hge_ctx, self.dir() + '/openapi_multiple_endpoints_same_path.yaml', transport)
|
check_query_f(hge_ctx, self.dir() + '/openapi_multiple_endpoints_same_path.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_complex_arg(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_get_endpoint_test_complex_arg.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_complex_args(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_get_endpoint_test_complex_args.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_recursive_arg(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_post_endpoint_test_recursive_arg.yaml', transport)
|
||||||
|
Loading…
Reference in New Issue
Block a user