mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
[server] add openapi support
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/1935 Co-authored-by: paritosh-08 <85472423+paritosh-08@users.noreply.github.com> Co-authored-by: pranshi06 <85474619+pranshi06@users.noreply.github.com> Co-authored-by: Lyndon Maydwell <92299+sordina@users.noreply.github.com> GitOrigin-RevId: 3e43b84d4e9e181b405855704112b49467dafdf9
This commit is contained in:
parent
49c07c79e5
commit
8b60122b9e
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Next release
|
## Next release
|
||||||
(Add entries below in the order of server, console, cli, docs, others)
|
(Add entries below in the order of server, console, cli, docs, others)
|
||||||
|
- server: add support for openapi json of REST Endpoints
|
||||||
|
|
||||||
- server: enable inherited roles by default in the graphql-engine
|
- server: enable inherited roles by default in the graphql-engine
|
||||||
- server: support MSSQL insert mutations
|
- server: support MSSQL insert mutations
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
active-repositories: hackage.haskell.org:merge
|
active-repositories: hackage.haskell.org:merge
|
||||||
constraints: any.Cabal ==3.2.0.0,
|
constraints: any.Cabal ==3.2.0.0,
|
||||||
any.Glob ==0.10.1,
|
|
||||||
any.HTTP ==4000.3.15,
|
any.HTTP ==4000.3.15,
|
||||||
HTTP -conduit10 -mtl1 +network-uri -warn-as-error -warp-tests,
|
HTTP -conduit10 -mtl1 +network-uri -warn-as-error -warp-tests,
|
||||||
any.HUnit ==1.6.2.0,
|
any.HUnit ==1.6.2.0,
|
||||||
@ -12,13 +11,12 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
SHA -exe,
|
SHA -exe,
|
||||||
any.Spock-core ==0.14.0.0,
|
any.Spock-core ==0.14.0.0,
|
||||||
any.StateVar ==1.2.1,
|
any.StateVar ==1.2.1,
|
||||||
any.abstract-deque ==0.3,
|
|
||||||
abstract-deque -usecas,
|
|
||||||
any.abstract-par ==0.3.3,
|
|
||||||
any.adjunctions ==4.4,
|
any.adjunctions ==4.4,
|
||||||
any.aeson ==1.5.5.1,
|
any.aeson ==1.5.5.1,
|
||||||
aeson -bytestring-builder -cffi -developer -fast,
|
aeson -bytestring-builder -cffi -developer -fast,
|
||||||
any.aeson-casing ==0.2.0.0,
|
any.aeson-casing ==0.2.0.0,
|
||||||
|
any.aeson-pretty ==0.8.8,
|
||||||
|
aeson-pretty -lib-only,
|
||||||
any.ansi-terminal ==0.11,
|
any.ansi-terminal ==0.11,
|
||||||
ansi-terminal -example,
|
ansi-terminal -example,
|
||||||
any.ansi-wl-pprint ==0.6.9,
|
any.ansi-wl-pprint ==0.6.9,
|
||||||
@ -50,7 +48,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.bifunctors ==5.5.10,
|
any.bifunctors ==5.5.10,
|
||||||
bifunctors +semigroups +tagged,
|
bifunctors +semigroups +tagged,
|
||||||
any.binary ==0.8.8.0,
|
any.binary ==0.8.8.0,
|
||||||
any.binary-orphans ==1.0.1,
|
|
||||||
any.binary-parser ==0.5.6,
|
any.binary-parser ==0.5.6,
|
||||||
any.blaze-builder ==0.4.2.1,
|
any.blaze-builder ==0.4.2.1,
|
||||||
any.blaze-html ==0.9.1.2,
|
any.blaze-html ==0.9.1.2,
|
||||||
@ -67,8 +64,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.cabal-doctest ==1.0.8,
|
any.cabal-doctest ==1.0.8,
|
||||||
any.call-stack ==0.3.0,
|
any.call-stack ==0.3.0,
|
||||||
any.case-insensitive ==1.2.1.0,
|
any.case-insensitive ==1.2.1.0,
|
||||||
any.cassava ==0.5.2.0,
|
|
||||||
cassava -bytestring--lt-0_10_4,
|
|
||||||
any.cereal ==0.5.8.1,
|
any.cereal ==0.5.8.1,
|
||||||
cereal -bytestring-builder,
|
cereal -bytestring-builder,
|
||||||
any.charset ==0.3.7.1,
|
any.charset ==0.3.7.1,
|
||||||
@ -77,7 +72,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
clock -llvm,
|
clock -llvm,
|
||||||
any.cmdargs ==0.10.20,
|
any.cmdargs ==0.10.20,
|
||||||
cmdargs +quotation -testprog,
|
cmdargs +quotation -testprog,
|
||||||
any.code-page ==0.2.1,
|
|
||||||
any.colour ==2.3.5,
|
any.colour ==2.3.5,
|
||||||
any.comonad ==5.0.8,
|
any.comonad ==5.0.8,
|
||||||
comonad +containers +distributive +indexed-traversable,
|
comonad +containers +distributive +indexed-traversable,
|
||||||
@ -93,10 +87,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.contravariant ==1.5.3,
|
any.contravariant ==1.5.3,
|
||||||
contravariant +semigroups +statevar +tagged,
|
contravariant +semigroups +statevar +tagged,
|
||||||
any.cookie ==0.4.5,
|
any.cookie ==0.4.5,
|
||||||
any.criterion ==1.5.9.0,
|
|
||||||
criterion -embed-data-files -fast,
|
|
||||||
any.criterion-measurement ==0.1.2.0,
|
|
||||||
criterion-measurement -fast,
|
|
||||||
any.cron ==0.7.0,
|
any.cron ==0.7.0,
|
||||||
cron -lib-werror,
|
cron -lib-werror,
|
||||||
any.crypto-api ==0.13.3,
|
any.crypto-api ==0.13.3,
|
||||||
@ -121,7 +111,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.data-textual ==0.3.0.3,
|
any.data-textual ==0.3.0.3,
|
||||||
any.deepseq ==1.4.4.0,
|
any.deepseq ==1.4.4.0,
|
||||||
any.deferred-folds ==0.9.15,
|
any.deferred-folds ==0.9.15,
|
||||||
any.dense-linear-algebra ==0.1.0.0,
|
|
||||||
any.dependent-map ==0.4.0.0,
|
any.dependent-map ==0.4.0.0,
|
||||||
any.dependent-sum ==0.7.1.0,
|
any.dependent-sum ==0.7.1.0,
|
||||||
any.directory ==1.3.6.0,
|
any.directory ==1.3.6.0,
|
||||||
@ -152,6 +141,7 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.foldl ==1.4.10,
|
any.foldl ==1.4.10,
|
||||||
any.formatting ==7.1.1,
|
any.formatting ==7.1.1,
|
||||||
any.free ==5.1.6,
|
any.free ==5.1.6,
|
||||||
|
any.generics-sop ==0.5.1.1,
|
||||||
any.ghc ==8.10.2,
|
any.ghc ==8.10.2,
|
||||||
any.ghc-boot ==8.10.2,
|
any.ghc-boot ==8.10.2,
|
||||||
any.ghc-boot-th ==8.10.2,
|
any.ghc-boot-th ==8.10.2,
|
||||||
@ -192,6 +182,7 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.http-conduit ==2.3.7.4,
|
any.http-conduit ==2.3.7.4,
|
||||||
http-conduit +aeson,
|
http-conduit +aeson,
|
||||||
any.http-date ==0.0.10,
|
any.http-date ==0.0.10,
|
||||||
|
any.http-media ==0.8.0.0,
|
||||||
any.http-types ==0.12.3,
|
any.http-types ==0.12.3,
|
||||||
any.http2 ==2.0.5,
|
any.http2 ==2.0.5,
|
||||||
http2 -devel,
|
http2 -devel,
|
||||||
@ -201,6 +192,7 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.indexed-traversable ==0.1.1,
|
any.indexed-traversable ==0.1.1,
|
||||||
any.insert-ordered-containers ==0.2.3.1,
|
any.insert-ordered-containers ==0.2.3.1,
|
||||||
any.inspection-testing ==0.4.5.0,
|
any.inspection-testing ==0.4.5.0,
|
||||||
|
inspection-testing -more-tests -old-text-tests,
|
||||||
any.integer-gmp ==1.0.3.0,
|
any.integer-gmp ==1.0.3.0,
|
||||||
any.integer-logarithms ==1.0.3.1,
|
any.integer-logarithms ==1.0.3.1,
|
||||||
integer-logarithms -check-bounds +integer-gmp,
|
integer-logarithms -check-bounds +integer-gmp,
|
||||||
@ -211,7 +203,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.iproute ==1.7.10,
|
any.iproute ==1.7.10,
|
||||||
any.jose ==0.8.4,
|
any.jose ==0.8.4,
|
||||||
jose -demos,
|
jose -demos,
|
||||||
any.js-chart ==2.9.4.1,
|
|
||||||
any.kan-extensions ==5.2.1,
|
any.kan-extensions ==5.2.1,
|
||||||
any.keys ==3.12.3,
|
any.keys ==3.12.3,
|
||||||
any.kriti-lang ==0.2.0.0,
|
any.kriti-lang ==0.2.0.0,
|
||||||
@ -232,15 +223,11 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
math-functions +system-erf +system-expm1,
|
math-functions +system-erf +system-expm1,
|
||||||
any.memory ==0.15.0,
|
any.memory ==0.15.0,
|
||||||
memory +support_basement +support_bytestring +support_deepseq +support_foundation,
|
memory +support_basement +support_bytestring +support_deepseq +support_foundation,
|
||||||
any.microstache ==1.0.1.2,
|
|
||||||
any.mime-types ==0.1.0.9,
|
any.mime-types ==0.1.0.9,
|
||||||
any.mmorph ==1.1.4,
|
any.mmorph ==1.1.4,
|
||||||
any.monad-control ==1.0.2.3,
|
any.monad-control ==1.0.2.3,
|
||||||
any.monad-loops ==0.4.3,
|
any.monad-loops ==0.4.3,
|
||||||
monad-loops +base4,
|
monad-loops +base4,
|
||||||
any.monad-par ==0.3.5,
|
|
||||||
monad-par -chaselev -newgeneric,
|
|
||||||
any.monad-par-extras ==0.3.3,
|
|
||||||
any.monad-time ==0.3.1.0,
|
any.monad-time ==0.3.1.0,
|
||||||
any.monad-validate ==1.2.0.0,
|
any.monad-validate ==1.2.0.0,
|
||||||
any.mono-traversable ==1.0.15.1,
|
any.mono-traversable ==1.0.15.1,
|
||||||
@ -248,7 +235,6 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.mtl-compat ==0.2.2,
|
any.mtl-compat ==0.2.2,
|
||||||
mtl-compat -two-point-one -two-point-two,
|
mtl-compat -two-point-one -two-point-two,
|
||||||
any.mustache ==2.3.1,
|
any.mustache ==2.3.1,
|
||||||
any.mwc-probability ==2.3.1,
|
|
||||||
any.mwc-random ==0.14.0.0,
|
any.mwc-random ==0.14.0.0,
|
||||||
any.raw-strings-qq ==1.1,
|
any.raw-strings-qq ==1.1,
|
||||||
any.mysql ==0.2.0.1,
|
any.mysql ==0.2.0.1,
|
||||||
@ -265,8 +251,10 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.odbc ==0.2.3,
|
any.odbc ==0.2.3,
|
||||||
any.old-locale ==1.0.0.7,
|
any.old-locale ==1.0.0.7,
|
||||||
any.old-time ==1.1.0.3,
|
any.old-time ==1.1.0.3,
|
||||||
|
any.openapi3 ==3.1.0,
|
||||||
any.optics-core ==0.3.0.1,
|
any.optics-core ==0.3.0.1,
|
||||||
any.optics-extra ==0.3,
|
any.optics-extra ==0.3,
|
||||||
|
any.optics-th ==0.3.0.2,
|
||||||
any.optparse-applicative ==0.16.1.0,
|
any.optparse-applicative ==0.16.1.0,
|
||||||
optparse-applicative +process,
|
optparse-applicative +process,
|
||||||
any.optparse-generic ==1.4.4,
|
any.optparse-generic ==1.4.4,
|
||||||
@ -286,11 +274,12 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.postgresql-libpq ==0.9.4.3,
|
any.postgresql-libpq ==0.9.4.3,
|
||||||
postgresql-libpq -use-pkg-config,
|
postgresql-libpq -use-pkg-config,
|
||||||
any.pretty ==1.1.3.6,
|
any.pretty ==1.1.3.6,
|
||||||
any.pretty-simple ==4.0.0.0,
|
|
||||||
any.pretty-show ==1.10,
|
any.pretty-show ==1.10,
|
||||||
|
any.pretty-simple ==4.0.0.0,
|
||||||
|
pretty-simple -buildexample -buildexe,
|
||||||
any.prettyprinter ==1.7.0,
|
any.prettyprinter ==1.7.0,
|
||||||
prettyprinter -buildreadme,
|
prettyprinter -buildreadme,
|
||||||
prettyprinter-ansi-terminal ==1.1.2,
|
any.prettyprinter-ansi-terminal ==1.1.2,
|
||||||
any.primitive ==0.7.1.0,
|
any.primitive ==0.7.1.0,
|
||||||
any.primitive-extras ==0.8,
|
any.primitive-extras ==0.8,
|
||||||
any.primitive-unlifted ==0.1.3.0,
|
any.primitive-unlifted ==0.1.3.0,
|
||||||
@ -341,10 +330,10 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.socks ==0.6.1,
|
any.socks ==0.6.1,
|
||||||
any.some ==1.0.1,
|
any.some ==1.0.1,
|
||||||
some +newtype-unsafe,
|
some +newtype-unsafe,
|
||||||
|
any.sop-core ==0.5.0.1,
|
||||||
any.split ==0.2.3.4,
|
any.split ==0.2.3.4,
|
||||||
any.splitmix ==0.1.0.3,
|
any.splitmix ==0.1.0.3,
|
||||||
splitmix -optimised-mixer,
|
splitmix -optimised-mixer,
|
||||||
any.statistics ==0.15.2.0,
|
|
||||||
any.stm ==2.5.0.0,
|
any.stm ==2.5.0.0,
|
||||||
any.stm-containers ==1.2,
|
any.stm-containers ==1.2,
|
||||||
any.stm-hamt ==1.2.0.4,
|
any.stm-hamt ==1.2.0.4,
|
||||||
@ -365,8 +354,8 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
any.tasty-smallcheck ==0.8.2,
|
any.tasty-smallcheck ==0.8.2,
|
||||||
any.template-haskell ==2.16.0.0,
|
any.template-haskell ==2.16.0.0,
|
||||||
any.temporary ==1.3,
|
any.temporary ==1.3,
|
||||||
any.terminfo ==0.4.1.4,
|
|
||||||
any.terminal-size ==0.3.2.1,
|
any.terminal-size ==0.3.2.1,
|
||||||
|
any.terminfo ==0.4.1.4,
|
||||||
any.text ==1.2.3.2,
|
any.text ==1.2.3.2,
|
||||||
any.text-builder ==0.6.6.1,
|
any.text-builder ==0.6.6.1,
|
||||||
any.text-conversions ==0.3.1,
|
any.text-conversions ==0.3.1,
|
||||||
@ -418,10 +407,8 @@ constraints: any.Cabal ==3.2.0.0,
|
|||||||
vector +boundschecks -internalchecks -unsafechecks -wall,
|
vector +boundschecks -internalchecks -unsafechecks -wall,
|
||||||
any.vector-algorithms ==0.8.0.4,
|
any.vector-algorithms ==0.8.0.4,
|
||||||
vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks,
|
vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks,
|
||||||
any.vector-binary-instances ==0.2.5.1,
|
|
||||||
any.vector-instances ==3.4,
|
any.vector-instances ==3.4,
|
||||||
vector-instances +hashable,
|
vector-instances +hashable,
|
||||||
any.vector-th-unbox ==0.2.1.7,
|
|
||||||
any.void ==0.7.3,
|
any.void ==0.7.3,
|
||||||
void -safe,
|
void -safe,
|
||||||
any.wai ==3.2.3,
|
any.wai ==3.2.3,
|
||||||
|
@ -99,3 +99,60 @@ Response
|
|||||||
The response is determined by the saved query. The response will be the same as if you had made the query directly in the GraphQL console.
|
The response is determined by the saved query. The response will be the same as if you had made the query directly in the GraphQL console.
|
||||||
|
|
||||||
See the :ref:`api_reference_graphql` for more details.
|
See the :ref:`api_reference_graphql` for more details.
|
||||||
|
|
||||||
|
|
||||||
|
OpenAPI 3 Specification
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The OpenAPI 3 specification of the REST endpoints are exposed at ``/api/swagger/json`` for admin role only:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
|
GET /api/swagger/json HTTP/1.1
|
||||||
|
X-Hasura-Role: admin
|
||||||
|
|
||||||
|
The response JSON will be a OpenAPI 3 specification (OAS 3.0) for all the
|
||||||
|
RESTified GraphQL Endpoints for admin roles. For more details about OAS 3.0,
|
||||||
|
`click here <https://swagger.io/specification/>`__.
|
||||||
|
|
||||||
|
Sample request
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
|
GET /api/swagger/json HTTP/1.1
|
||||||
|
X-Hasura-Role: admin
|
||||||
|
|
||||||
|
Response
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: JSON
|
||||||
|
|
||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"version": "",
|
||||||
|
"title": "Rest Endpoints",
|
||||||
|
"description": "These OpenAPI specifications are automatically generated by Hasura."
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/rest/users": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Fetch user data",
|
||||||
|
"description": "This API fetches user data (first name and last name) from the users table.\n***\nThe GraphQl query for this endpoint is:\n``` graphql\nquery MyQuery{\n users {\n first_name\n last_name\n }\n}\n```",
|
||||||
|
"responses": {}
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {}
|
||||||
|
}
|
||||||
|
@ -131,6 +131,7 @@ library
|
|||||||
, monad-loops
|
, monad-loops
|
||||||
, monad-validate
|
, monad-validate
|
||||||
, mtl
|
, mtl
|
||||||
|
, openapi3
|
||||||
, optparse-applicative
|
, optparse-applicative
|
||||||
, parsec
|
, parsec
|
||||||
, pg-client
|
, pg-client
|
||||||
@ -217,7 +218,7 @@ library
|
|||||||
, template-haskell >= 2.11
|
, template-haskell >= 2.11
|
||||||
|
|
||||||
-- websockets interface related
|
-- websockets interface related
|
||||||
, websockets
|
, websockets>=0.12
|
||||||
, stm
|
, stm
|
||||||
, stm-containers
|
, stm-containers
|
||||||
, list-t
|
, list-t
|
||||||
@ -440,6 +441,7 @@ library
|
|||||||
, Hasura.Server.Types
|
, Hasura.Server.Types
|
||||||
, Hasura.Server.API.PGDump
|
, Hasura.Server.API.PGDump
|
||||||
, Hasura.Server.Rest
|
, Hasura.Server.Rest
|
||||||
|
, Hasura.Server.OpenAPI
|
||||||
, Hasura.Prelude
|
, Hasura.Prelude
|
||||||
|
|
||||||
, Hasura.EncJSON
|
, Hasura.EncJSON
|
||||||
|
@ -21,6 +21,7 @@ module Hasura.RQL.Types.Endpoint
|
|||||||
deName,
|
deName,
|
||||||
splitPath,
|
splitPath,
|
||||||
mkEndpointUrl,
|
mkEndpointUrl,
|
||||||
|
unEndpointUrl,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ module Hasura.RQL.Types.Endpoint.Trie
|
|||||||
singletonMultiMap,
|
singletonMultiMap,
|
||||||
singletonTrie,
|
singletonTrie,
|
||||||
insertPath,
|
insertPath,
|
||||||
|
leaves,
|
||||||
matchPath,
|
matchPath,
|
||||||
ambiguousPaths,
|
ambiguousPaths,
|
||||||
ambiguousPathsGrouped,
|
ambiguousPathsGrouped,
|
||||||
@ -210,3 +211,6 @@ ambiguousPaths (Trie pathMap (MultiMap methodMap)) =
|
|||||||
childNodeAmbiguousPaths pc t = first (pc :) <$> ambiguousPaths (mergeWildcardTrie t)
|
childNodeAmbiguousPaths pc t = first (pc :) <$> ambiguousPaths (mergeWildcardTrie t)
|
||||||
wildcardTrie = M.lookup PathParam pathMap
|
wildcardTrie = M.lookup PathParam pathMap
|
||||||
mergeWildcardTrie = maybe id (<>) wildcardTrie
|
mergeWildcardTrie = maybe id (<>) wildcardTrie
|
||||||
|
|
||||||
|
leaves :: Trie k v -> [v]
|
||||||
|
leaves (Trie m v) = v : concatMap leaves (M.elems m)
|
||||||
|
@ -60,6 +60,7 @@ import Hasura.Server.Limits
|
|||||||
import Hasura.Server.Logging
|
import Hasura.Server.Logging
|
||||||
import Hasura.Server.Metrics (ServerMetrics)
|
import Hasura.Server.Metrics (ServerMetrics)
|
||||||
import Hasura.Server.Middleware (corsMiddleware)
|
import Hasura.Server.Middleware (corsMiddleware)
|
||||||
|
import Hasura.Server.OpenAPI (serveJSON)
|
||||||
import Hasura.Server.Rest
|
import Hasura.Server.Rest
|
||||||
import Hasura.Server.Types
|
import Hasura.Server.Types
|
||||||
import Hasura.Server.Utils
|
import Hasura.Server.Utils
|
||||||
@ -1077,6 +1078,13 @@ httpApp setupHook corsCfg serverCtx enableConsole consoleAssetsDir enableTelemet
|
|||||||
onlyAdmin
|
onlyAdmin
|
||||||
respJ <- liftIO $ EL.dumpLiveQueriesState True $ scLQState serverCtx
|
respJ <- liftIO $ EL.dumpLiveQueriesState True $ scLQState serverCtx
|
||||||
return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue respJ) [])
|
return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue respJ) [])
|
||||||
|
Spock.get "api/swagger/json" $
|
||||||
|
spockAction encodeQErr id $
|
||||||
|
mkGetHandler $ do
|
||||||
|
onlyAdmin
|
||||||
|
sc <- getSCFromRef $ scCacheRef serverCtx
|
||||||
|
let json = serveJSON sc
|
||||||
|
return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue json) [])
|
||||||
|
|
||||||
forM_ [Spock.GET, Spock.POST] $ \m -> Spock.hookAny m $ \_ -> do
|
forM_ [Spock.GET, Spock.POST] $ \m -> Spock.hookAny m $ \_ -> do
|
||||||
req <- Spock.request
|
req <- Spock.request
|
||||||
|
189
server/src-lib/Hasura/Server/OpenAPI.hs
Normal file
189
server/src-lib/Hasura/Server/OpenAPI.hs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Hasura.Server.OpenAPI (serveJSON) where
|
||||||
|
|
||||||
|
import Control.Lens
|
||||||
|
import Data.Aeson (Value, toJSON)
|
||||||
|
import Data.HashMap.Strict qualified as M
|
||||||
|
import Data.HashMap.Strict.InsOrd qualified as MI
|
||||||
|
import Data.List.NonEmpty qualified as LNE
|
||||||
|
import Data.OpenApi
|
||||||
|
import Data.OpenApi.Declare
|
||||||
|
import Data.Set.Internal qualified as S
|
||||||
|
import Data.Text qualified as T
|
||||||
|
import Data.Text.NonEmpty qualified as TNE
|
||||||
|
import Hasura.Prelude hiding (get, put)
|
||||||
|
import Hasura.RQL.Types.Endpoint
|
||||||
|
import Hasura.RQL.Types.QueryCollection
|
||||||
|
import Hasura.RQL.Types.SchemaCache
|
||||||
|
import Language.GraphQL.Draft.Syntax qualified as G
|
||||||
|
|
||||||
|
data EndpointData = EndpointData
|
||||||
|
{ endpointUrl :: String,
|
||||||
|
method :: [Text],
|
||||||
|
varList :: [Referenced Param],
|
||||||
|
endpointDescription :: Text, -- contains API comments and graphql query
|
||||||
|
endpointName :: Text
|
||||||
|
}
|
||||||
|
|
||||||
|
getVarList :: EndpointMetadata GQLQueryWithText -> [G.VariableDefinition]
|
||||||
|
getVarList e = vars =<< varLists
|
||||||
|
where
|
||||||
|
varLists = G.getExecutableDefinitions . unGQLQuery . getGQLQuery . _edQuery . _ceDefinition $ e
|
||||||
|
vars x = case x of
|
||||||
|
G.ExecutableDefinitionOperation (G.OperationDefinitionTyped (G.TypedOperationDefinition _ _ vds _ _)) -> vds
|
||||||
|
_ -> []
|
||||||
|
|
||||||
|
getVariableDefinitions :: EndpointMetadata GQLQueryWithText -> [Referenced Param]
|
||||||
|
getVariableDefinitions d = fmap varDetails varList
|
||||||
|
where
|
||||||
|
pathVars = map T.tail $ lefts $ splitPath Left Right (_ceUrl d) -- NOTE: URL Variable name ':' prefix is removed for `elem` lookup.
|
||||||
|
varList = getVarList d
|
||||||
|
varDetails a =
|
||||||
|
let vName = (G.unName . G._vdName $ a)
|
||||||
|
in Inline $
|
||||||
|
mkParam
|
||||||
|
vName
|
||||||
|
Nothing
|
||||||
|
Nothing
|
||||||
|
(if vName `elem` pathVars then ParamPath else ParamQuery)
|
||||||
|
Nothing
|
||||||
|
(getDefaultVar a)
|
||||||
|
( case G._vdType a of
|
||||||
|
G.TypeNamed _ na -> case G.unName na of
|
||||||
|
"Int" -> Just OpenApiInteger
|
||||||
|
"String" -> Just OpenApiString
|
||||||
|
"json" -> Just OpenApiObject
|
||||||
|
_ -> Nothing
|
||||||
|
G.TypeList _ _ -> Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
getGQLQueryFromTrie :: EndpointMetadata GQLQueryWithText -> Text
|
||||||
|
getGQLQueryFromTrie = getGQLQueryText . _edQuery . _ceDefinition
|
||||||
|
|
||||||
|
mkParam :: Text -> Maybe Text -> Maybe Bool -> ParamLocation -> Maybe Bool -> Maybe Value -> Maybe OpenApiType -> Param
|
||||||
|
mkParam nameP desc req loc allowEmpty def varType =
|
||||||
|
mempty
|
||||||
|
& name .~ nameP
|
||||||
|
& description .~ desc
|
||||||
|
& required .~ req
|
||||||
|
& in_ .~ loc
|
||||||
|
& allowEmptyValue .~ allowEmpty
|
||||||
|
& schema
|
||||||
|
?~ Inline
|
||||||
|
( mempty
|
||||||
|
& default_ .~ def
|
||||||
|
& type_ .~ varType
|
||||||
|
)
|
||||||
|
|
||||||
|
getDefaultVar :: G.VariableDefinition -> Maybe Value
|
||||||
|
getDefaultVar var = case G._vdDefaultValue var of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just va -> case va of
|
||||||
|
G.VNull -> Nothing
|
||||||
|
G.VInt n -> Just $ toJSON n
|
||||||
|
G.VFloat sci -> Just $ toJSON sci
|
||||||
|
G.VString txt -> Just $ toJSON txt
|
||||||
|
G.VBoolean b -> Just $ toJSON b
|
||||||
|
G.VEnum ev -> Just $ toJSON ev
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
getComment :: EndpointMetadata GQLQueryWithText -> Text
|
||||||
|
getComment d = comment
|
||||||
|
where
|
||||||
|
gql = getGQLQueryFromTrie d
|
||||||
|
comment = case _ceComment d of
|
||||||
|
(Just c) -> c <> "\n***\nThe GraphQl query for this endpoint is:\n``` graphql\n" <> gql <> "\n```"
|
||||||
|
Nothing -> "***\nThe GraphQl query for this endpoint is:\n``` graphql\n" <> gql <> "\n```"
|
||||||
|
|
||||||
|
getURL :: EndpointMetadata GQLQueryWithText -> Text
|
||||||
|
getURL d =
|
||||||
|
"/api/rest/"
|
||||||
|
-- The url will be of the format <Endpoint>/:<Var1>/:<Var2> ... always, so we can
|
||||||
|
-- split and take the first element (it should never fail)
|
||||||
|
<> fst (T.breakOn "/" (TNE.unNonEmptyText . unEndpointUrl . _ceUrl $ d))
|
||||||
|
<> foldl
|
||||||
|
( \b a -> b <> "/{" <> a <> "}"
|
||||||
|
)
|
||||||
|
""
|
||||||
|
(map T.tail $ lefts $ splitPath Left Right (_ceUrl d))
|
||||||
|
|
||||||
|
extractEndpointInfo :: EndpointMethod -> EndpointMetadata GQLQueryWithText -> EndpointData
|
||||||
|
extractEndpointInfo method d =
|
||||||
|
let endpointUrl = T.unpack . getURL $ d
|
||||||
|
varList = getVariableDefinitions d
|
||||||
|
endpointDescription = getComment d
|
||||||
|
endpointName = TNE.unNonEmptyText $ unEndpointName $ _ceName d
|
||||||
|
in EndpointData
|
||||||
|
{ endpointUrl = endpointUrl,
|
||||||
|
method = [unEndpointMethod method], -- NOTE: Methods are grouped with into matching endpoints - Name used for grouping.
|
||||||
|
varList = varList,
|
||||||
|
endpointDescription = endpointDescription,
|
||||||
|
endpointName = endpointName
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndpointsData :: SchemaCache -> [EndpointData]
|
||||||
|
getEndpointsData sc = map squashEndpointGroup endpointsGrouped
|
||||||
|
where
|
||||||
|
endpointTrie = scEndpoints sc
|
||||||
|
methodMaps = leaves endpointTrie
|
||||||
|
endpointsWithMethods = concatMap (\(m, s) -> map (m,) (S.toList s)) $ concatMap (M.toList . _unMultiMap) methodMaps
|
||||||
|
endpointsWithInfo = map (uncurry extractEndpointInfo) endpointsWithMethods
|
||||||
|
endpointsGrouped = LNE.groupBy (\a b -> endpointName a == endpointName b) endpointsWithInfo
|
||||||
|
|
||||||
|
squashEndpointGroup :: NonEmpty EndpointData -> EndpointData
|
||||||
|
squashEndpointGroup g = (LNE.head g) {method = concatMap method g}
|
||||||
|
|
||||||
|
serveJSON :: SchemaCache -> OpenApi
|
||||||
|
serveJSON sc = spec & components . schemas .~ defs
|
||||||
|
where
|
||||||
|
(defs, spec) = runDeclare (declareOpenApiSpec sc) mempty
|
||||||
|
|
||||||
|
declareOpenApiSpec :: SchemaCache -> Declare (Definitions Schema) OpenApi
|
||||||
|
declareOpenApiSpec sc = do
|
||||||
|
let mkOperation :: EndpointData -> Operation
|
||||||
|
mkOperation ed =
|
||||||
|
mempty
|
||||||
|
& description ?~ endpointDescription ed
|
||||||
|
& summary ?~ endpointName ed
|
||||||
|
|
||||||
|
getOPName :: EndpointData -> Text -> Maybe Operation
|
||||||
|
getOPName ed methodType =
|
||||||
|
if methodType `elem` method ed
|
||||||
|
then Just $ mkOperation ed
|
||||||
|
else Nothing
|
||||||
|
|
||||||
|
xHasuraAS :: Param
|
||||||
|
xHasuraAS =
|
||||||
|
mkParam
|
||||||
|
"x-hasura-admin-secret"
|
||||||
|
(Just "Your x-hasura-admin-secret will be used for authentication of the API request.")
|
||||||
|
Nothing
|
||||||
|
ParamHeader
|
||||||
|
Nothing
|
||||||
|
Nothing
|
||||||
|
(Just OpenApiString)
|
||||||
|
|
||||||
|
generatePathItem :: EndpointData -> PathItem
|
||||||
|
generatePathItem ed =
|
||||||
|
mempty
|
||||||
|
& get .~ getOPName ed "GET"
|
||||||
|
& post .~ getOPName ed "POST"
|
||||||
|
& put .~ getOPName ed "PUT"
|
||||||
|
& delete .~ getOPName ed "DELETE"
|
||||||
|
& patch .~ getOPName ed "PATCH"
|
||||||
|
& parameters .~ Inline xHasuraAS :
|
||||||
|
varList ed
|
||||||
|
|
||||||
|
endpointLst = getEndpointsData sc
|
||||||
|
|
||||||
|
mkOpenAPISchema :: [EndpointData] -> InsOrdHashMap FilePath PathItem
|
||||||
|
mkOpenAPISchema edLst = foldl (\hm ed -> MI.insert (endpointUrl ed) (generatePathItem ed) hm) mempty edLst
|
||||||
|
|
||||||
|
openAPIPaths = mkOpenAPISchema endpointLst
|
||||||
|
|
||||||
|
return $
|
||||||
|
mempty
|
||||||
|
& paths .~ openAPIPaths
|
||||||
|
& info . title .~ "Rest Endpoints"
|
||||||
|
& info . description ?~ "These OpenAPI specifications are automatically generated by Hasura."
|
12
server/tests-py/queries/openapi/openapi_empty.yaml
Normal file
12
server/tests-py/queries/openapi/openapi_empty.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
description: Call openapi json endpoint for empty response
|
||||||
|
url: /api/swagger/json
|
||||||
|
method: GET
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
response:
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: ''
|
||||||
|
title: Rest Endpoints
|
||||||
|
description: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
components: {}
|
@ -0,0 +1,137 @@
|
|||||||
|
- description: Try to add a rest endpoint with multiple methods
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: multi_method_endpoint
|
||||||
|
name: multi_method_endpoint
|
||||||
|
methods:
|
||||||
|
- GET
|
||||||
|
- POST
|
||||||
|
- PUT
|
||||||
|
- DELETE
|
||||||
|
- PATCH
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: mutation_with_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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/multi_method_endpoint:
|
||||||
|
get:
|
||||||
|
summary: multi_method_endpoint
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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: {}
|
||||||
|
put:
|
||||||
|
summary: multi_method_endpoint
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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: {}
|
||||||
|
post:
|
||||||
|
summary: multi_method_endpoint
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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: {}
|
||||||
|
delete:
|
||||||
|
summary: multi_method_endpoint
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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: {}
|
||||||
|
patch:
|
||||||
|
summary: multi_method_endpoint
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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:
|
||||||
|
- 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
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: last_name
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: multi_method_endpoint
|
@ -0,0 +1,59 @@
|
|||||||
|
- 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: simple
|
||||||
|
name: simple
|
||||||
|
methods:
|
||||||
|
- GET
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: simple_query
|
||||||
|
|
||||||
|
|
||||||
|
- 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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/simple:
|
||||||
|
get:
|
||||||
|
summary: simple
|
||||||
|
description: |-
|
||||||
|
***
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
``` graphql
|
||||||
|
query { test_table { first_name last_name } }
|
||||||
|
```
|
||||||
|
responses: {}
|
||||||
|
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.
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: simple
|
@ -0,0 +1,186 @@
|
|||||||
|
- description: Try to add a POST rest endpoint with arguments in URL
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: with_args_url/:first_name/:last_name
|
||||||
|
name: with_args_url
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: query_with_args
|
||||||
|
|
||||||
|
- description: Try to add a POST rest endpoint with default argument
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: with_default_arg
|
||||||
|
name: with_default_arg
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: query_with_default_arg
|
||||||
|
|
||||||
|
- description: Try to add a POST rest mutation endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: mutation_with_args
|
||||||
|
name: mutation_with_args
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: mutation_with_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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/mutation_with_args:
|
||||||
|
post:
|
||||||
|
summary: mutation_with_args
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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:
|
||||||
|
- 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
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: last_name
|
||||||
|
/api/rest/with_default_arg:
|
||||||
|
post:
|
||||||
|
summary: with_default_arg
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
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:
|
||||||
|
- 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:
|
||||||
|
default: Foo
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: first_name
|
||||||
|
'/api/rest/with_args_url/{first_name}/{last_name}':
|
||||||
|
post:
|
||||||
|
summary: with_args_url
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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:
|
||||||
|
- 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: path
|
||||||
|
name: first_name
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: path
|
||||||
|
name: last_name
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: with_args_url
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: with_default_arg
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: mutation_with_args
|
@ -0,0 +1,73 @@
|
|||||||
|
- 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: with_args
|
||||||
|
name: with_args
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: query_with_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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/with_args:
|
||||||
|
post:
|
||||||
|
summary: with_args
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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:
|
||||||
|
- 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
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: last_name
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: with_args
|
@ -0,0 +1,73 @@
|
|||||||
|
- description: Try to add a POST rest endpoint with arguments in URL
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: with_args_url/:first_name/:last_name
|
||||||
|
name: with_args_url
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: query_with_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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
'/api/rest/with_args_url/{first_name}/{last_name}':
|
||||||
|
post:
|
||||||
|
summary: with_args_url
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
The GraphQl query for this endpoint is:
|
||||||
|
|
||||||
|
``` 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:
|
||||||
|
- 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: path
|
||||||
|
name: first_name
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
in: path
|
||||||
|
name: last_name
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: with_args_url
|
@ -0,0 +1,69 @@
|
|||||||
|
- description: Try to add a POST rest endpoint with default argument
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: create_rest_endpoint
|
||||||
|
args:
|
||||||
|
url: with_default_arg
|
||||||
|
name: with_default_arg
|
||||||
|
methods:
|
||||||
|
- POST
|
||||||
|
definition:
|
||||||
|
query:
|
||||||
|
collection_name: test_collection
|
||||||
|
query_name: query_with_default_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: These OpenAPI specifications are automatically generated by Hasura.
|
||||||
|
paths:
|
||||||
|
/api/rest/with_default_arg:
|
||||||
|
post:
|
||||||
|
summary: with_default_arg
|
||||||
|
description: >-
|
||||||
|
***
|
||||||
|
|
||||||
|
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:
|
||||||
|
- 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:
|
||||||
|
default: Foo
|
||||||
|
type: string
|
||||||
|
in: query
|
||||||
|
name: first_name
|
||||||
|
components: {}
|
||||||
|
|
||||||
|
- description: Try to remove the endpoint
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: drop_rest_endpoint
|
||||||
|
args:
|
||||||
|
name: with_default_arg
|
42
server/tests-py/queries/openapi/setup.yaml
Normal file
42
server/tests-py/queries/openapi/setup.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
type: bulk
|
||||||
|
args:
|
||||||
|
- type: create_query_collection
|
||||||
|
args:
|
||||||
|
name: test_collection
|
||||||
|
definition:
|
||||||
|
queries:
|
||||||
|
- name: simple_query
|
||||||
|
query: "query { test_table { first_name last_name } }"
|
||||||
|
- name: query_with_arg
|
||||||
|
query: "query ($first_name:String!) { test_table(where: {first_name: { _eq: $first_name } }) { first_name last_name } }"
|
||||||
|
- name: query_with_args
|
||||||
|
query: "query ($first_name: String!, $last_name:String!) { test_table(where: {first_name: { _eq: $first_name } last_name: { _eq: $last_name }}) { first_name last_name } }"
|
||||||
|
- name: query_with_uuid_arg
|
||||||
|
query: "query ($id: uuid!) { test_table(where: {id: { _neq: $id }}) { first_name last_name } }"
|
||||||
|
- name: query_with_default_arg
|
||||||
|
query: "query ($first_name:String=\"Foo\") { test_table(where: {first_name: { _eq: $first_name } }) { first_name last_name } }"
|
||||||
|
- 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 } }"
|
||||||
|
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
create table test_table(
|
||||||
|
first_name text,
|
||||||
|
last_name text,
|
||||||
|
id UUID NOT NULL DEFAULT gen_random_uuid()
|
||||||
|
);
|
||||||
|
|
||||||
|
- type: track_table
|
||||||
|
args:
|
||||||
|
schema: public
|
||||||
|
name: test_table
|
||||||
|
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
insert into test_table (first_name, last_name)
|
||||||
|
values
|
||||||
|
('Foo', 'Bar'),
|
||||||
|
('Baz', 'Qux'),
|
||||||
|
('X%20Y', 'Test');
|
10
server/tests-py/queries/openapi/teardown.yaml
Normal file
10
server/tests-py/queries/openapi/teardown.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
type: bulk
|
||||||
|
args:
|
||||||
|
- type: drop_query_collection
|
||||||
|
args:
|
||||||
|
collection: test_collection
|
||||||
|
cascade: false
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
drop table test_table
|
34
server/tests-py/test_openapi.py
Normal file
34
server/tests-py/test_openapi.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
from validate import check_query_f, check_query, get_conf_f
|
||||||
|
from context import PytestConf
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("transport", ['http'])
|
||||||
|
@pytest.mark.usefixtures('per_class_tests_db_state')
|
||||||
|
class TestOpenAPISpec:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dir(cls):
|
||||||
|
return 'queries/openapi'
|
||||||
|
|
||||||
|
def test_empty_openapi_json(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_empty.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_simple(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_get_endpoint_test_simple.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_args(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_post_endpoint_test_with_args.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_args_url(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_post_endpoint_test_with_args_url.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_default_arg(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_post_endpoint_test_with_default_arg.yaml', transport)
|
||||||
|
|
||||||
|
def test_endpoint_with_multiple_methods(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_endpoint_with_multiple_methods.yaml', transport)
|
||||||
|
|
||||||
|
def test_multiple_endpoints(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/openapi_multiple_endpoints_test.yaml', transport)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user