зеркало из https://github.com/microsoft/CCF.git
Produce OpenAPI document describing CCF's endpoints (#1612)
This commit is contained in:
Родитель
e499f62f8d
Коммит
37d78ecfe2
|
@ -250,6 +250,11 @@ if(BUILD_TESTS)
|
|||
json_schema ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/json_schema.cpp
|
||||
)
|
||||
|
||||
add_unit_test(
|
||||
openapi_test ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/openapi.cpp
|
||||
)
|
||||
target_link_libraries(openapi_test PRIVATE http_parser.host)
|
||||
|
||||
add_unit_test(
|
||||
logger_json_test
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/logger_json_test.cpp
|
||||
|
@ -265,7 +270,7 @@ if(BUILD_TESTS)
|
|||
)
|
||||
use_client_mbedtls(kv_test)
|
||||
target_link_libraries(
|
||||
kv_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} secp256k1.host
|
||||
kv_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} secp256k1.host http_parser.host
|
||||
)
|
||||
|
||||
add_unit_test(
|
||||
|
@ -311,6 +316,7 @@ if(BUILD_TESTS)
|
|||
target_include_directories(history_test PRIVATE ${EVERCRYPT_INC})
|
||||
target_link_libraries(
|
||||
history_test PRIVATE ${CRYPTO_LIBRARY} evercrypt.host secp256k1.host
|
||||
http_parser.host
|
||||
)
|
||||
|
||||
add_unit_test(
|
||||
|
@ -330,7 +336,9 @@ if(BUILD_TESTS)
|
|||
historical_queries_test
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/historical_queries.cpp
|
||||
)
|
||||
target_link_libraries(historical_queries_test PRIVATE secp256k1.host)
|
||||
target_link_libraries(
|
||||
historical_queries_test PRIVATE secp256k1.host http_parser.host
|
||||
)
|
||||
|
||||
add_unit_test(
|
||||
snapshot_test ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snapshot.cpp
|
||||
|
@ -427,7 +435,7 @@ if(BUILD_TESTS)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/lua_interp/test/lua_kv.cpp
|
||||
)
|
||||
target_include_directories(lua_test PRIVATE ${LUA_DIR})
|
||||
target_link_libraries(lua_test PRIVATE lua.host)
|
||||
target_link_libraries(lua_test PRIVATE lua.host http_parser.host)
|
||||
|
||||
add_unit_test(
|
||||
merkle_test ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/merkle_test.cpp
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"signed_req": {
|
||||
"properties": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"state_digest": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ack/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"method": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"params_schema": {
|
||||
"$ref": "http://json-schema.org/draft-07/schema#"
|
||||
"type": "object"
|
||||
},
|
||||
"result_schema": {
|
||||
"$ref": "http://json-schema.org/draft-07/schema#"
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"endpoints": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"verb": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"verb",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"endpoints"
|
||||
],
|
||||
"title": "api/result",
|
||||
"type": "object"
|
||||
}
|
|
@ -0,0 +1,971 @@
|
|||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"CallerInfo": {
|
||||
"properties": {
|
||||
"caller_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"caller_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodeStatus": {
|
||||
"enum": [
|
||||
"ACCEPTED",
|
||||
"RETIRED"
|
||||
]
|
||||
},
|
||||
"EndpointMetrics__Metric": {
|
||||
"properties": {
|
||||
"calls": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"errors": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"failures": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"calls",
|
||||
"errors",
|
||||
"failures"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EndpointMetrics__Out": {
|
||||
"properties": {
|
||||
"metrics": {
|
||||
"$ref": "#/components/schemas/named_named_EndpointMetrics__Metric"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"metrics"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Out": {
|
||||
"properties": {
|
||||
"versions": {
|
||||
"$ref": "#/components/schemas/GetCode__Version_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"versions"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Version": {
|
||||
"properties": {
|
||||
"digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/CodeStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Version_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetCode__Version"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetCommit__Out": {
|
||||
"properties": {
|
||||
"seqno": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"view": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"view",
|
||||
"seqno"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetMetrics__HistogramResults": {
|
||||
"properties": {
|
||||
"buckets": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
},
|
||||
"high": {
|
||||
"$ref": "#/components/schemas/int32"
|
||||
},
|
||||
"low": {
|
||||
"$ref": "#/components/schemas/int32"
|
||||
},
|
||||
"overflow": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"underflow": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"low",
|
||||
"high",
|
||||
"overflow",
|
||||
"underflow",
|
||||
"buckets"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetMetrics__Out": {
|
||||
"properties": {
|
||||
"histogram": {
|
||||
"$ref": "#/components/schemas/GetMetrics__HistogramResults"
|
||||
},
|
||||
"tx_rates": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"histogram",
|
||||
"tx_rates"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNetworkInfo__NodeInfo": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"port": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"host",
|
||||
"port"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNetworkInfo__NodeInfo_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__NodeInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetNetworkInfo__Out": {
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__NodeInfo_array"
|
||||
},
|
||||
"primary_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"nodes",
|
||||
"primary_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNodesByRPCAddress__NodeInfo": {
|
||||
"properties": {
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/NodeStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNodesByRPCAddress__NodeInfo_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetNodesByRPCAddress__Out": {
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"nodes"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetPrimaryInfo__Out": {
|
||||
"properties": {
|
||||
"current_view": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"primary_host": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"primary_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"primary_port": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"primary_id",
|
||||
"primary_host",
|
||||
"primary_port",
|
||||
"current_view"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetReceipt__Out": {
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"$ref": "#/components/schemas/uint8_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"receipt"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetSchema__Out": {
|
||||
"properties": {
|
||||
"params_schema": {
|
||||
"$ref": "#/components/schemas/json_schema"
|
||||
},
|
||||
"result_schema": {
|
||||
"$ref": "#/components/schemas/json_schema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"params_schema",
|
||||
"result_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetTxStatus__Out": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/TxStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"LoggingGet__Out": {
|
||||
"properties": {
|
||||
"msg": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"msg"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"LoggingRecord__In": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"msg": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"msg"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NodeStatus": {
|
||||
"enum": [
|
||||
"PENDING",
|
||||
"TRUSTED",
|
||||
"RETIRED"
|
||||
]
|
||||
},
|
||||
"TxStatus": {
|
||||
"enum": [
|
||||
"UNKNOWN",
|
||||
"PENDING",
|
||||
"COMMITTED",
|
||||
"INVALID"
|
||||
]
|
||||
},
|
||||
"VerifyReceipt__In": {
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"$ref": "#/components/schemas/uint8_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"receipt"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"VerifyReceipt__Out": {
|
||||
"properties": {
|
||||
"valid": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"valid"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"boolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"int32": {
|
||||
"maximum": 2147483647,
|
||||
"minimum": -2147483648,
|
||||
"type": "integer"
|
||||
},
|
||||
"int64": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
},
|
||||
"json": {},
|
||||
"json_schema": {
|
||||
"type": "object"
|
||||
},
|
||||
"named_EndpointMetrics__Metric": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/EndpointMetrics__Metric"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"named_named_EndpointMetrics__Metric": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/named_EndpointMetrics__Metric"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"string": {
|
||||
"type": "string"
|
||||
},
|
||||
"uint64": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"uint8": {
|
||||
"maximum": 255,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"uint8_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/uint8"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.",
|
||||
"title": "CCF Sample Logging App",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/api": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/schema": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "method",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetSchema__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/code": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetCode__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/commit": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetCommit__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/endpoint_metrics": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EndpointMetrics__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/local_tx": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "seqno",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "view",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTxStatus__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/log/private": {
|
||||
"delete": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoggingGet__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoggingRecord__In"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Auto-generated request body schema"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/log/private/admin_only": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoggingRecord__In"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Auto-generated request body schema"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/log/private/anonymous": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoggingRecord__In"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Auto-generated request body schema"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/log/private/raw_text/{id}": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"/log/public": {
|
||||
"delete": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"msg"
|
||||
],
|
||||
"title": "log/public/result",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"msg"
|
||||
],
|
||||
"title": "log/public/params",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "log/public/result",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/metrics": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetMetrics__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/network_info": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/node/ids": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "host",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "port",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/primary_info": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetPrimaryInfo__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/receipt": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "commit",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetReceipt__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/receipt/verify": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VerifyReceipt__In"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Auto-generated request body schema"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VerifyReceipt__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tx": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "seqno",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "view",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTxStatus__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user_id": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "cert",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"items": {
|
||||
"maximum": 255,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CallerInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "/app"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"versions": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"seqno": {
|
||||
"maximum": 9223372036854775807,
|
||||
|
|
|
@ -1,52 +1,35 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"metrics": {
|
||||
"items": {
|
||||
"items": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"items": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"calls": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"errors": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"failures": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"calls",
|
||||
"errors",
|
||||
"failures"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"type": "array"
|
||||
"additionalProperties": {
|
||||
"additionalProperties": {
|
||||
"properties": {
|
||||
"calls": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"type": "array"
|
||||
"errors": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"failures": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"calls",
|
||||
"errors",
|
||||
"failures"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"seqno": {
|
||||
"maximum": 9223372036854775807,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"status": {
|
||||
"enum": [
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/private/admin_only/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/private/anonymous/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/private/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/private/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/private/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/public/result",
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/public/result",
|
||||
"type": "bool"
|
||||
"type": "boolean"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"histogram": {
|
||||
"properties": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"items": {
|
||||
|
|
|
@ -0,0 +1,785 @@
|
|||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"CodeStatus": {
|
||||
"enum": [
|
||||
"ACCEPTED",
|
||||
"RETIRED"
|
||||
]
|
||||
},
|
||||
"EndpointMetrics__Metric": {
|
||||
"properties": {
|
||||
"calls": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"errors": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"failures": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"calls",
|
||||
"errors",
|
||||
"failures"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EndpointMetrics__Out": {
|
||||
"properties": {
|
||||
"metrics": {
|
||||
"$ref": "#/components/schemas/named_named_EndpointMetrics__Metric"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"metrics"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Out": {
|
||||
"properties": {
|
||||
"versions": {
|
||||
"$ref": "#/components/schemas/GetCode__Version_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"versions"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Version": {
|
||||
"properties": {
|
||||
"digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/CodeStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetCode__Version_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetCode__Version"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetCommit__Out": {
|
||||
"properties": {
|
||||
"seqno": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"view": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"view",
|
||||
"seqno"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetMetrics__HistogramResults": {
|
||||
"properties": {
|
||||
"buckets": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
},
|
||||
"high": {
|
||||
"$ref": "#/components/schemas/int32"
|
||||
},
|
||||
"low": {
|
||||
"$ref": "#/components/schemas/int32"
|
||||
},
|
||||
"overflow": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"underflow": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"low",
|
||||
"high",
|
||||
"overflow",
|
||||
"underflow",
|
||||
"buckets"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetMetrics__Out": {
|
||||
"properties": {
|
||||
"histogram": {
|
||||
"$ref": "#/components/schemas/GetMetrics__HistogramResults"
|
||||
},
|
||||
"tx_rates": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"histogram",
|
||||
"tx_rates"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNetworkInfo__NodeInfo": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"port": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"host",
|
||||
"port"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNetworkInfo__NodeInfo_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__NodeInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetNetworkInfo__Out": {
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__NodeInfo_array"
|
||||
},
|
||||
"primary_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"nodes",
|
||||
"primary_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNodesByRPCAddress__NodeInfo": {
|
||||
"properties": {
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/NodeStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetNodesByRPCAddress__NodeInfo_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetNodesByRPCAddress__Out": {
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"nodes"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetPrimaryInfo__Out": {
|
||||
"properties": {
|
||||
"current_view": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"primary_host": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"primary_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"primary_port": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"primary_id",
|
||||
"primary_host",
|
||||
"primary_port",
|
||||
"current_view"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetQuotes__Out": {
|
||||
"properties": {
|
||||
"quotes": {
|
||||
"$ref": "#/components/schemas/GetQuotes__Quote_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"quotes"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetQuotes__Quote": {
|
||||
"properties": {
|
||||
"error": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"mrenclave": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"raw": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node_id",
|
||||
"raw"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetQuotes__Quote_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GetQuotes__Quote"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GetReceipt__Out": {
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"$ref": "#/components/schemas/uint8_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"receipt"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetSchema__Out": {
|
||||
"properties": {
|
||||
"params_schema": {
|
||||
"$ref": "#/components/schemas/json_schema"
|
||||
},
|
||||
"result_schema": {
|
||||
"$ref": "#/components/schemas/json_schema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"params_schema",
|
||||
"result_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetState__Out": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/uint64"
|
||||
},
|
||||
"last_recovered_seqno": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"last_signed_seqno": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"recovery_target_seqno": {
|
||||
"$ref": "#/components/schemas/int64"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/components/schemas/ccf__State"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"state",
|
||||
"last_signed_seqno"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GetTxStatus__Out": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/TxStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NodeStatus": {
|
||||
"enum": [
|
||||
"PENDING",
|
||||
"TRUSTED",
|
||||
"RETIRED"
|
||||
]
|
||||
},
|
||||
"TxStatus": {
|
||||
"enum": [
|
||||
"UNKNOWN",
|
||||
"PENDING",
|
||||
"COMMITTED",
|
||||
"INVALID"
|
||||
]
|
||||
},
|
||||
"VerifyReceipt__In": {
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"$ref": "#/components/schemas/uint8_array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"receipt"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"VerifyReceipt__Out": {
|
||||
"properties": {
|
||||
"valid": {
|
||||
"$ref": "#/components/schemas/boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"valid"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"boolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ccf__State": {
|
||||
"enum": [
|
||||
"uninitialized",
|
||||
"initialized",
|
||||
"pending",
|
||||
"partOfPublicNetwork",
|
||||
"partOfNetwork",
|
||||
"readingPublicLedger",
|
||||
"readingPrivateLedger"
|
||||
]
|
||||
},
|
||||
"int32": {
|
||||
"maximum": 2147483647,
|
||||
"minimum": -2147483648,
|
||||
"type": "integer"
|
||||
},
|
||||
"int64": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
},
|
||||
"json": {},
|
||||
"json_schema": {
|
||||
"type": "object"
|
||||
},
|
||||
"named_EndpointMetrics__Metric": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/EndpointMetrics__Metric"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"named_named_EndpointMetrics__Metric": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/named_EndpointMetrics__Metric"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"string": {
|
||||
"type": "string"
|
||||
},
|
||||
"uint64": {
|
||||
"maximum": 18446744073709551615,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"uint8": {
|
||||
"maximum": 255,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"uint8_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/uint8"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"description": "This API provides public, uncredentialed access to service and node state.",
|
||||
"title": "CCF Public Node API",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/api": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/schema": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "method",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetSchema__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/code": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetCode__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/commit": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetCommit__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/endpoint_metrics": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EndpointMetrics__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/local_tx": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "seqno",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "view",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTxStatus__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/metrics": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetMetrics__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/network_info": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetNetworkInfo__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/node/ids": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "host",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "port",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetNodesByRPCAddress__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/primary_info": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetPrimaryInfo__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/quote": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetQuotes__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/quotes": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetQuotes__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/receipt": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "commit",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetReceipt__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/receipt/verify": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VerifyReceipt__In"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Auto-generated request body schema"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VerifyReceipt__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/state": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetState__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tx": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "seqno",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "view",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"maximum": 9223372036854775807,
|
||||
"minimum": -9223372036854775808,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetTxStatus__Out"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Default response description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "/node"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"current_view": {
|
||||
"maximum": 9223372036854775807,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"proposal_id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"ballot": {
|
||||
"properties": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"ballot": {
|
||||
"properties": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"proposal_id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"proposal_id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"parameter": {},
|
||||
"proposer": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"ballot": {
|
||||
"properties": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"proposal_id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"bytecode": {
|
||||
"items": {
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "query/result"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"quotes": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"quotes": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"key": {},
|
||||
"table": {
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "read/result"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"commit": {
|
||||
"maximum": 9223372036854775807,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"receipt": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "recovery_share/submit/params",
|
||||
"type": "string"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "recovery_share/submit/result",
|
||||
"type": "string"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"encrypted_recovery_share": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"seqno": {
|
||||
"maximum": 9223372036854775807,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"status": {
|
||||
"enum": [
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cert": {
|
||||
"items": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"caller_id": {
|
||||
"maximum": 18446744073709551615,
|
||||
|
|
|
@ -655,27 +655,63 @@ namespace ccfapp
|
|||
set_default(default_handler);
|
||||
}
|
||||
|
||||
static std::pair<http_method, std::string> split_script_key(
|
||||
const std::string& key)
|
||||
{
|
||||
size_t s = key.find(' ');
|
||||
if (s != std::string::npos)
|
||||
{
|
||||
return std::make_pair(
|
||||
http::http_method_from_str(key.substr(0, s).c_str()),
|
||||
key.substr(s + 1, key.size() - (s + 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_pair(HTTP_POST, key);
|
||||
}
|
||||
}
|
||||
|
||||
// Since we do our own dispatch within the default handler, report the
|
||||
// supported methods here
|
||||
void list_methods(kv::Tx& tx, ListMethods::Out& out) override
|
||||
void build_api(nlohmann::json& document, kv::Tx& tx) override
|
||||
{
|
||||
UserEndpointRegistry::list_methods(tx, out);
|
||||
UserEndpointRegistry::build_api(document, tx);
|
||||
|
||||
auto scripts = tx.get_view(this->network.app_scripts);
|
||||
scripts->foreach([&out](const auto& key, const auto&) {
|
||||
size_t s = key.find(' ');
|
||||
if (s != std::string::npos)
|
||||
{
|
||||
out.endpoints.push_back(
|
||||
{key.substr(0, s), key.substr(s + 1, key.size() - (s + 1))});
|
||||
}
|
||||
else
|
||||
{
|
||||
out.endpoints.push_back({"POST", key});
|
||||
}
|
||||
scripts->foreach([&document](const auto& key, const auto&) {
|
||||
const auto [verb, method] = split_script_key(key);
|
||||
|
||||
ds::openapi::path_operation(ds::openapi::path(document, method), verb);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
nlohmann::json get_endpoint_schema(
|
||||
kv::Tx& tx, const GetSchema::In& in) override
|
||||
{
|
||||
auto j = UserEndpointRegistry::get_endpoint_schema(tx, in);
|
||||
|
||||
auto scripts = tx.get_view(this->network.app_scripts);
|
||||
scripts->foreach([&j, &in](const auto& key, const auto&) {
|
||||
const auto [verb, method] = split_script_key(key);
|
||||
|
||||
if (in.method == method)
|
||||
{
|
||||
std::string verb_name = http_method_str(verb);
|
||||
nonstd::to_lower(verb_name);
|
||||
// We have no schema for JS endpoints, but populate the object if we
|
||||
// know about them
|
||||
GetSchema::Out out;
|
||||
out.params_schema.schema = nullptr;
|
||||
out.result_schema.schema = nullptr;
|
||||
j[verb_name] = out;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return j;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
|
|
@ -69,6 +69,12 @@ namespace loggingapp
|
|||
get_public_params_schema(nlohmann::json::parse(j_get_public_in)),
|
||||
get_public_result_schema(nlohmann::json::parse(j_get_public_out))
|
||||
{
|
||||
openapi_info.title = "CCF Sample Logging App";
|
||||
openapi_info.description =
|
||||
"This CCF sample app implements a simple logging application, securely "
|
||||
"recording messages at client-specified IDs. It demonstrates most of "
|
||||
"the features available to CCF apps.";
|
||||
|
||||
// SNIPPET_START: record
|
||||
auto record = [this](kv::Tx& tx, nlohmann::json&& params) {
|
||||
// SNIPPET_START: macro_validation_record
|
||||
|
|
|
@ -52,7 +52,6 @@ namespace loggingapp
|
|||
// Manual schemas, verified then parsed in handler
|
||||
static const std::string j_record_public_in = R"!!!(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
|
@ -72,15 +71,13 @@ namespace loggingapp
|
|||
|
||||
static const std::string j_record_public_out = R"!!!(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "log/public/result",
|
||||
"type": "bool"
|
||||
"type": "boolean"
|
||||
}
|
||||
)!!!";
|
||||
|
||||
static const std::string j_get_public_in = R"!!!(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
|
@ -96,7 +93,6 @@ namespace loggingapp
|
|||
|
||||
static const std::string j_get_public_out = R"!!!(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"msg": {
|
||||
"type": "string"
|
||||
|
|
|
@ -185,18 +185,63 @@ namespace ccfapp
|
|||
set_default(json_adapter(default_handler));
|
||||
}
|
||||
|
||||
static std::pair<http_method, std::string> split_script_key(
|
||||
const std::string& key)
|
||||
{
|
||||
size_t s = key.find(' ');
|
||||
if (s != std::string::npos)
|
||||
{
|
||||
return std::make_pair(
|
||||
http::http_method_from_str(key.substr(0, s).c_str()),
|
||||
key.substr(s + 1, key.size() - (s + 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_pair(HTTP_POST, key);
|
||||
}
|
||||
}
|
||||
|
||||
// Since we do our own dispatch within the default handler, report the
|
||||
// supported methods here
|
||||
void list_methods(kv::Tx& tx, ListMethods::Out& out) override
|
||||
void build_api(nlohmann::json& document, kv::Tx& tx) override
|
||||
{
|
||||
UserEndpointRegistry::list_methods(tx, out);
|
||||
UserEndpointRegistry::build_api(document, tx);
|
||||
|
||||
auto scripts = tx.get_view(this->network.app_scripts);
|
||||
scripts->foreach([&out](const auto& key, const auto&) {
|
||||
out.endpoints.push_back({"POST", key});
|
||||
scripts->foreach([&document](const auto& key, const auto&) {
|
||||
const auto [verb, method] = split_script_key(key);
|
||||
|
||||
ds::openapi::path_operation(ds::openapi::path(document, method), verb);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
nlohmann::json get_endpoint_schema(
|
||||
kv::Tx& tx, const GetSchema::In& in) override
|
||||
{
|
||||
auto j = UserEndpointRegistry::get_endpoint_schema(tx, in);
|
||||
|
||||
auto scripts = tx.get_view(this->network.app_scripts);
|
||||
scripts->foreach([&j, &in](const auto& key, const auto&) {
|
||||
const auto [verb, method] = split_script_key(key);
|
||||
|
||||
if (in.method == method)
|
||||
{
|
||||
std::string verb_name = http_method_str(verb);
|
||||
nonstd::to_lower(verb_name);
|
||||
// We have no schema for JS endpoints, but populate the object if we
|
||||
// know about them
|
||||
GetSchema::Out out;
|
||||
out.params_schema.schema = nullptr;
|
||||
out.result_schema.schema = nullptr;
|
||||
j[verb_name] = out;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return j;
|
||||
}
|
||||
};
|
||||
|
||||
class Lua : public ccf::UserRpcFrontend
|
||||
|
|
127
src/ds/json.h
127
src/ds/json.h
|
@ -373,6 +373,35 @@ namespace std
|
|||
#define FILL_SCHEMA_OPTIONAL_FOR_JSON_FINAL(TYPE, FIELD) \
|
||||
FILL_SCHEMA_OPTIONAL_WITH_RENAMES_FOR_JSON_FINAL(TYPE, FIELD, FIELD)
|
||||
|
||||
#define ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES_FOR_JSON_NEXT( \
|
||||
TYPE, C_FIELD, JSON_FIELD) \
|
||||
j["properties"][#JSON_FIELD] = \
|
||||
doc.template add_schema_component<decltype(TYPE::C_FIELD)>(); \
|
||||
j["required"].push_back(#JSON_FIELD);
|
||||
#define ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES_FOR_JSON_FINAL( \
|
||||
TYPE, C_FIELD, JSON_FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES_FOR_JSON_NEXT( \
|
||||
TYPE, C_FIELD, JSON_FIELD)
|
||||
|
||||
#define ADD_SCHEMA_COMPONENTS_REQUIRED_FOR_JSON_NEXT(TYPE, FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES_FOR_JSON_NEXT(TYPE, FIELD, FIELD)
|
||||
#define ADD_SCHEMA_COMPONENTS_REQUIRED_FOR_JSON_FINAL(TYPE, FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES_FOR_JSON_FINAL(TYPE, FIELD, FIELD)
|
||||
|
||||
#define ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES_FOR_JSON_NEXT( \
|
||||
TYPE, C_FIELD, JSON_FIELD) \
|
||||
j["properties"][#JSON_FIELD] = \
|
||||
doc.template add_schema_component<decltype(TYPE::C_FIELD)>();
|
||||
#define ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES_FOR_JSON_FINAL( \
|
||||
TYPE, C_FIELD, JSON_FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES_FOR_JSON_NEXT( \
|
||||
TYPE, C_FIELD, JSON_FIELD)
|
||||
|
||||
#define ADD_SCHEMA_COMPONENTS_OPTIONAL_FOR_JSON_NEXT(TYPE, FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES_FOR_JSON_NEXT(TYPE, FIELD, FIELD)
|
||||
#define ADD_SCHEMA_COMPONENTS_OPTIONAL_FOR_JSON_FINAL(TYPE, FIELD) \
|
||||
ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES_FOR_JSON_FINAL(TYPE, FIELD, FIELD)
|
||||
|
||||
#define JSON_FIELD_FOR_JSON_NEXT(TYPE, FIELD) \
|
||||
JsonField<decltype(TYPE::FIELD)>{#FIELD},
|
||||
#define JSON_FIELD_FOR_JSON_FINAL(TYPE, FIELD) \
|
||||
|
@ -381,16 +410,24 @@ namespace std
|
|||
# FIELD \
|
||||
}
|
||||
|
||||
/** Defines from_json, to_json, and fill_json_schema functions for struct/class
|
||||
* types, converting member fields to JSON elements. Missing elements will cause
|
||||
* errors to be raised. This assumes that from_json, to_json, and
|
||||
* fill_json_schema are implemented for each member field type, either manually
|
||||
* or through these macros.
|
||||
/** Defines from_json, to_json, fill_json_schema, and schema_name functions for
|
||||
* struct/class types, converting member fields to JSON elements. Missing
|
||||
* elements will cause errors to be raised. This assumes that from_json,
|
||||
* to_json, and fill_json_schema are implemented for each member field type,
|
||||
* either manually or through these macros.
|
||||
* // clang-format off
|
||||
* ie, the following must compile, for each foo in T:
|
||||
* T t; nlohmann::json j, schema;
|
||||
* j["foo"] = t.foo;
|
||||
* t.foo = j["foo"].get<decltype(T::foo)>();
|
||||
* fill_json_schema(schema, t);
|
||||
* std::string s = schema_name(t.foo);
|
||||
* // clang-format on
|
||||
*
|
||||
* Optional fields will be inserted into the JSON object iff their value differs
|
||||
* from the value in a default-constructed instance of T. So if optional fields
|
||||
* are present, then T must be default-constructible and the optional fields
|
||||
* must be distinguishable (have operator!= defined)
|
||||
*
|
||||
* To use:
|
||||
* - Declare struct as normal
|
||||
|
@ -479,13 +516,21 @@ namespace std
|
|||
PRE_FROM_JSON, \
|
||||
POST_FROM_JSON, \
|
||||
PRE_FILL_SCHEMA, \
|
||||
POST_FILL_SCHEMA) \
|
||||
POST_FILL_SCHEMA, \
|
||||
PRE_ADD_SCHEMA, \
|
||||
POST_ADD_SCHEMA) \
|
||||
void to_json_required_fields(nlohmann::json& j, const TYPE& t); \
|
||||
void to_json_optional_fields(nlohmann::json& j, const TYPE& t); \
|
||||
void from_json_required_fields(const nlohmann::json& j, TYPE& t); \
|
||||
void from_json_optional_fields(const nlohmann::json& j, TYPE& t); \
|
||||
void fill_json_schema_required_fields(nlohmann::json& j, const TYPE& t); \
|
||||
void fill_json_schema_optional_fields(nlohmann::json& j, const TYPE& t); \
|
||||
template <typename T> \
|
||||
void add_schema_components_required_fields( \
|
||||
T& doc, nlohmann::json& j, const TYPE& t); \
|
||||
template <typename T> \
|
||||
void add_schema_components_optional_fields( \
|
||||
T& doc, nlohmann::json& j, const TYPE& t); \
|
||||
inline void to_json(nlohmann::json& j, const TYPE& t) \
|
||||
{ \
|
||||
PRE_TO_JSON; \
|
||||
|
@ -503,9 +548,20 @@ namespace std
|
|||
PRE_FILL_SCHEMA; \
|
||||
fill_json_schema_required_fields(j, t); \
|
||||
POST_FILL_SCHEMA; \
|
||||
} \
|
||||
inline std::string schema_name(const TYPE&) \
|
||||
{ \
|
||||
return #TYPE; \
|
||||
} \
|
||||
template <typename T> \
|
||||
void add_schema_components(T& doc, nlohmann::json& j, const TYPE& t) \
|
||||
{ \
|
||||
PRE_ADD_SCHEMA; \
|
||||
add_schema_components_required_fields(doc, j, t); \
|
||||
POST_ADD_SCHEMA; \
|
||||
}
|
||||
|
||||
#define DECLARE_JSON_TYPE(TYPE) DECLARE_JSON_TYPE_IMPL(TYPE, , , , , , )
|
||||
#define DECLARE_JSON_TYPE(TYPE) DECLARE_JSON_TYPE_IMPL(TYPE, , , , , , , , )
|
||||
|
||||
#define DECLARE_JSON_TYPE_WITH_BASE(TYPE, BASE) \
|
||||
DECLARE_JSON_TYPE_IMPL( \
|
||||
|
@ -514,7 +570,9 @@ namespace std
|
|||
, \
|
||||
from_json(j, static_cast<BASE&>(t)), \
|
||||
, \
|
||||
fill_json_schema(j, static_cast<const BASE&>(t)), )
|
||||
fill_json_schema(j, static_cast<const BASE&>(t)), \
|
||||
, \
|
||||
add_schema_components(doc, j, static_cast<const BASE&>(t)), )
|
||||
|
||||
#define DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(TYPE) \
|
||||
DECLARE_JSON_TYPE_IMPL( \
|
||||
|
@ -524,7 +582,9 @@ namespace std
|
|||
, \
|
||||
from_json_optional_fields(j, t), \
|
||||
, \
|
||||
fill_json_schema_optional_fields(j, t))
|
||||
fill_json_schema_optional_fields(j, t), \
|
||||
, \
|
||||
add_schema_components_optional_fields(doc, j, t))
|
||||
|
||||
#define DECLARE_JSON_TYPE_WITH_BASE_AND_OPTIONAL_FIELDS(TYPE, BASE) \
|
||||
DECLARE_JSON_TYPE_IMPL( \
|
||||
|
@ -534,10 +594,13 @@ namespace std
|
|||
from_json(j, static_cast<BASE&>(t)), \
|
||||
from_json_optional_fields(j, t), \
|
||||
fill_json_schema(j, static_cast<const BASE&>(t)), \
|
||||
fill_json_schema_optional_fields(j, t))
|
||||
fill_json_schema_optional_fields(j, t), \
|
||||
add_schema_components(doc, j, static_cast<const BASE&>(t)), \
|
||||
add_schema_components_optional_fields(doc, j, t))
|
||||
|
||||
#define DECLARE_JSON_REQUIRED_FIELDS(TYPE, ...) \
|
||||
inline void to_json_required_fields(nlohmann::json& j, const TYPE& t) \
|
||||
inline void to_json_required_fields( \
|
||||
nlohmann::json& j, [[maybe_unused]] const TYPE& t) \
|
||||
{ \
|
||||
if (!j.is_object()) \
|
||||
{ \
|
||||
|
@ -545,7 +608,8 @@ namespace std
|
|||
} \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__)(POP1)(WRITE_REQUIRED, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
inline void from_json_required_fields(const nlohmann::json& j, TYPE& t) \
|
||||
inline void from_json_required_fields( \
|
||||
const nlohmann::json& j, [[maybe_unused]] TYPE& t) \
|
||||
{ \
|
||||
if (!j.is_object()) \
|
||||
{ \
|
||||
|
@ -553,11 +617,22 @@ namespace std
|
|||
} \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__)(POP1)(READ_REQUIRED, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
inline void fill_json_schema_required_fields(nlohmann::json& j, const TYPE&) \
|
||||
inline void fill_json_schema_required_fields( \
|
||||
nlohmann::json& j, [[maybe_unused]] const TYPE& t) \
|
||||
{ \
|
||||
j["type"] = "object"; \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP1)(FILL_SCHEMA_REQUIRED, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
template <typename T> \
|
||||
void add_schema_components_required_fields( \
|
||||
[[maybe_unused]] T& doc, \
|
||||
nlohmann::json& j, \
|
||||
[[maybe_unused]] const TYPE& t) \
|
||||
{ \
|
||||
j["type"] = "object"; \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP1)(ADD_SCHEMA_COMPONENTS_REQUIRED, TYPE, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_JSON_REQUIRED_FIELDS_WITH_RENAMES(TYPE, ...) \
|
||||
|
@ -584,6 +659,14 @@ namespace std
|
|||
j["type"] = "object"; \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP2)(FILL_SCHEMA_REQUIRED_WITH_RENAMES, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
template <typename T> \
|
||||
void add_schema_components_required_fields( \
|
||||
T& doc, nlohmann::json& j, const TYPE& t) \
|
||||
{ \
|
||||
j["type"] = "object"; \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP2)(ADD_SCHEMA_COMPONENTS_REQUIRED_WITH_RENAMES, TYPE, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_JSON_OPTIONAL_FIELDS(TYPE, ...) \
|
||||
|
@ -600,6 +683,13 @@ namespace std
|
|||
{ \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP1)(FILL_SCHEMA_OPTIONAL, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
template <typename T> \
|
||||
void add_schema_components_optional_fields( \
|
||||
T& doc, nlohmann::json& j, const TYPE&) \
|
||||
{ \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP1)(ADD_SCHEMA_COMPONENTS_OPTIONAL, TYPE, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_JSON_OPTIONAL_FIELDS_WITH_RENAMES(TYPE, ...) \
|
||||
|
@ -619,10 +709,21 @@ namespace std
|
|||
{ \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP2)(FILL_SCHEMA_OPTIONAL_WITH_RENAMES, TYPE, ##__VA_ARGS__) \
|
||||
} \
|
||||
template <typename T> \
|
||||
void add_schema_components_optional_fields( \
|
||||
T& doc, nlohmann::json& j, const TYPE& t) \
|
||||
{ \
|
||||
_FOR_JSON_COUNT_NN(__VA_ARGS__) \
|
||||
(POP2)(ADD_SCHEMA_COMPONENTS_OPTIONAL_WITH_RENAMES, TYPE, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_JSON_ENUM(TYPE, ...) \
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(TYPE, __VA_ARGS__) \
|
||||
inline std::string schema_name(const TYPE&) \
|
||||
{ \
|
||||
return #TYPE; \
|
||||
} \
|
||||
inline void fill_enum_schema(nlohmann::json& j, const TYPE&) \
|
||||
{ \
|
||||
static const std::pair<TYPE, nlohmann::json> m[] = __VA_ARGS__; \
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "ds/nonstd.h"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace ds
|
||||
|
@ -36,6 +38,9 @@ namespace ds
|
|||
schema["maximum"] = std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string schema_name();
|
||||
|
||||
template <typename T>
|
||||
void fill_schema(nlohmann::json& schema);
|
||||
|
||||
|
@ -50,8 +55,23 @@ namespace ds
|
|||
return element;
|
||||
}
|
||||
|
||||
template <typename T, typename Doc>
|
||||
nlohmann::json schema_element()
|
||||
{
|
||||
auto element = nlohmann::json::object();
|
||||
fill_schema<T>(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
namespace adl
|
||||
{
|
||||
template <typename T>
|
||||
std::string schema_name()
|
||||
{
|
||||
T t;
|
||||
return schema_name(t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void fill_schema(nlohmann::json& schema)
|
||||
{
|
||||
|
@ -67,6 +87,103 @@ namespace ds
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string schema_name()
|
||||
{
|
||||
if constexpr (nonstd::is_specialization<T, std::optional>::value)
|
||||
{
|
||||
return schema_name<typename T::value_type>();
|
||||
}
|
||||
else if constexpr (nonstd::is_specialization<T, std::vector>::value)
|
||||
{
|
||||
return fmt::format("{}_array", schema_name<typename T::value_type>());
|
||||
}
|
||||
else if constexpr (
|
||||
nonstd::is_specialization<T, std::map>::value ||
|
||||
nonstd::is_specialization<T, std::unordered_map>::value)
|
||||
{
|
||||
if (std::is_same<typename T::key_type, std::string>::value)
|
||||
{
|
||||
return fmt::format(
|
||||
"named_{}", schema_name<typename T::mapped_type>());
|
||||
}
|
||||
else
|
||||
{
|
||||
return fmt::format(
|
||||
"{}_to_{}",
|
||||
schema_name<typename T::key_type>(),
|
||||
schema_name<typename T::mapped_type>());
|
||||
}
|
||||
}
|
||||
else if constexpr (nonstd::is_specialization<T, std::pair>::value)
|
||||
{
|
||||
return fmt::format(
|
||||
"{}_and_{}",
|
||||
schema_name<typename T::first_type>(),
|
||||
schema_name<typename T::second_type>());
|
||||
}
|
||||
else if constexpr (std::is_same<T, std::string>::value)
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
else if constexpr (std::is_same<T, bool>::value)
|
||||
{
|
||||
return "boolean";
|
||||
}
|
||||
else if constexpr (std::is_same<T, uint8_t>::value)
|
||||
{
|
||||
return "uint8";
|
||||
}
|
||||
else if constexpr (std::is_same<T, uint16_t>::value)
|
||||
{
|
||||
return "uint16";
|
||||
}
|
||||
else if constexpr (std::is_same<T, uint32_t>::value)
|
||||
{
|
||||
return "uint32";
|
||||
}
|
||||
else if constexpr (std::is_same<T, uint64_t>::value)
|
||||
{
|
||||
return "uint64";
|
||||
}
|
||||
else if constexpr (std::is_same<T, int8_t>::value)
|
||||
{
|
||||
return "int8";
|
||||
}
|
||||
else if constexpr (std::is_same<T, int16_t>::value)
|
||||
{
|
||||
return "int16";
|
||||
}
|
||||
else if constexpr (std::is_same<T, int32_t>::value)
|
||||
{
|
||||
return "int32";
|
||||
}
|
||||
else if constexpr (std::is_same<T, int64_t>::value)
|
||||
{
|
||||
return "int64";
|
||||
}
|
||||
else if constexpr (std::is_same<T, float>::value)
|
||||
{
|
||||
return "float";
|
||||
}
|
||||
else if constexpr (std::is_same<T, double>::value)
|
||||
{
|
||||
return "double";
|
||||
}
|
||||
else if constexpr (std::is_same<T, nlohmann::json>::value)
|
||||
{
|
||||
return "json";
|
||||
}
|
||||
else if constexpr (std::is_same<T, JsonSchema>::value)
|
||||
{
|
||||
return "json_schema";
|
||||
}
|
||||
else
|
||||
{
|
||||
return adl::schema_name<T>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void fill_schema(nlohmann::json& schema)
|
||||
{
|
||||
|
@ -83,18 +200,28 @@ namespace ds
|
|||
nonstd::is_specialization<T, std::map>::value ||
|
||||
nonstd::is_specialization<T, std::unordered_map>::value)
|
||||
{
|
||||
// Nlohmann serialises maps to an array of (K, V) pairs
|
||||
schema["type"] = "array";
|
||||
auto items = nlohmann::json::object();
|
||||
// Nlohmann serialises maps to an array of (K, V) pairs...
|
||||
if (std::is_same<typename T::key_type, std::string>::value)
|
||||
{
|
||||
items["type"] = "array";
|
||||
|
||||
auto sub_items = nlohmann::json::array();
|
||||
sub_items.push_back(schema_element<typename T::key_type>());
|
||||
sub_items.push_back(schema_element<typename T::mapped_type>());
|
||||
items["items"] = sub_items;
|
||||
// ...unless the keys are strings!
|
||||
schema["type"] = "object";
|
||||
schema["additionalProperties"] =
|
||||
schema_element<typename T::mapped_type>();
|
||||
}
|
||||
else
|
||||
{
|
||||
schema["type"] = "array";
|
||||
auto items = nlohmann::json::object();
|
||||
{
|
||||
items["type"] = "array";
|
||||
|
||||
auto sub_items = nlohmann::json::array();
|
||||
sub_items.push_back(schema_element<typename T::key_type>());
|
||||
sub_items.push_back(schema_element<typename T::mapped_type>());
|
||||
items["items"] = sub_items;
|
||||
}
|
||||
schema["items"] = items;
|
||||
}
|
||||
schema["items"] = items;
|
||||
}
|
||||
else if constexpr (nonstd::is_specialization<T, std::pair>::value)
|
||||
{
|
||||
|
@ -116,6 +243,7 @@ namespace ds
|
|||
{
|
||||
// Any field that contains more json is completely unconstrained, so we
|
||||
// do not add a type or any other fields
|
||||
schema = nlohmann::json::object();
|
||||
}
|
||||
else if constexpr (std::is_integral<T>::value)
|
||||
{
|
||||
|
@ -127,7 +255,7 @@ namespace ds
|
|||
}
|
||||
else if constexpr (std::is_same<T, JsonSchema>::value)
|
||||
{
|
||||
schema["$ref"] = JsonSchema::hyperschema;
|
||||
schema["type"] = "object";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -139,7 +267,6 @@ namespace ds
|
|||
inline nlohmann::json build_schema(const std::string& title)
|
||||
{
|
||||
nlohmann::json schema;
|
||||
schema["$schema"] = JsonSchema::hyperschema;
|
||||
schema["title"] = title;
|
||||
|
||||
fill_schema<T>(schema);
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
@ -59,4 +61,31 @@ namespace nonstd
|
|||
|
||||
template <class T>
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
/** converts strings to upper or lower case, in-place
|
||||
*/
|
||||
static inline void to_upper(std::string& s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
|
||||
return std::toupper(c);
|
||||
});
|
||||
}
|
||||
|
||||
static inline void to_lower(std::string& s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
}
|
||||
|
||||
static inline std::string remove_prefix(
|
||||
const std::string& s, const std::string& prefix)
|
||||
{
|
||||
if (s.find(prefix) == 0)
|
||||
{
|
||||
return s.substr(prefix.size());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ds/json.h"
|
||||
#include "ds/nonstd.h"
|
||||
|
||||
#include <http-parser/http_parser.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||
|
||||
namespace ds
|
||||
{
|
||||
/**
|
||||
* This namespace contains helper functions, structs, and templates for
|
||||
* modifying an OpenAPI JSON document. They do not set every field, but should
|
||||
* fill every _required_ field, and the resulting object can be further
|
||||
* modified by hand as required.
|
||||
*/
|
||||
namespace openapi
|
||||
{
|
||||
namespace access
|
||||
{
|
||||
static inline nlohmann::json& get_object(
|
||||
nlohmann::json& j, const std::string& k)
|
||||
{
|
||||
const auto ib = j.emplace(k, nlohmann::json::object());
|
||||
return ib.first.value();
|
||||
}
|
||||
|
||||
static inline nlohmann::json& get_array(
|
||||
nlohmann::json& j, const std::string& k)
|
||||
{
|
||||
const auto ib = j.emplace(k, nlohmann::json::array());
|
||||
return ib.first.value();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void check_path_valid(const std::string& s)
|
||||
{
|
||||
if (s.rfind("/", 0) != 0)
|
||||
{
|
||||
throw std::logic_error(
|
||||
fmt::format("'{}' is not a valid path - must begin with '/'", s));
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string remove_invalid_chars(const std::string_view& s_)
|
||||
{
|
||||
std::string s(s_);
|
||||
|
||||
for (auto& c : s)
|
||||
{
|
||||
if (c == ':')
|
||||
{
|
||||
c = '_';
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline nlohmann::json create_document(
|
||||
const std::string& title,
|
||||
const std::string& description,
|
||||
const std::string& document_version)
|
||||
{
|
||||
return nlohmann::json{{"openapi", "3.0.0"},
|
||||
{"info",
|
||||
{{"title", title},
|
||||
{"description", description},
|
||||
{"version", document_version}}},
|
||||
{"servers", nlohmann::json::array()},
|
||||
{"paths", nlohmann::json::object()}};
|
||||
}
|
||||
|
||||
static inline nlohmann::json& server(
|
||||
nlohmann::json& document, const std::string& url)
|
||||
{
|
||||
auto& servers = access::get_object(document, "servers");
|
||||
servers.push_back({{"url", url}});
|
||||
return servers.back();
|
||||
}
|
||||
|
||||
static inline nlohmann::json& path(
|
||||
nlohmann::json& document, const std::string& path)
|
||||
{
|
||||
auto p = remove_invalid_chars(path);
|
||||
if (p.find("/") != 0)
|
||||
{
|
||||
p = fmt::format("/{}", p);
|
||||
}
|
||||
|
||||
auto& paths = access::get_object(document, "paths");
|
||||
return access::get_object(paths, p);
|
||||
}
|
||||
|
||||
static inline nlohmann::json& path_operation(
|
||||
nlohmann::json& path, http_method verb)
|
||||
{
|
||||
// HTTP_GET becomes the string "get"
|
||||
std::string s = http_method_str(verb);
|
||||
nonstd::to_lower(s);
|
||||
auto& po = access::get_object(path, s);
|
||||
// responses is required field in a path_operation
|
||||
access::get_object(po, "responses");
|
||||
return po;
|
||||
}
|
||||
|
||||
static inline nlohmann::json& parameters(nlohmann::json& path_operation)
|
||||
{
|
||||
return access::get_array(path_operation, "parameters");
|
||||
}
|
||||
|
||||
static inline nlohmann::json& response(
|
||||
nlohmann::json& path_operation,
|
||||
http_status status,
|
||||
const std::string& description = "Default response description")
|
||||
{
|
||||
auto& responses = access::get_object(path_operation, "responses");
|
||||
// HTTP_STATUS_OK (aka an int-enum with value 200) becomes the string
|
||||
// "200"
|
||||
const auto s = std::to_string(status);
|
||||
auto& response = access::get_object(responses, s);
|
||||
response["description"] = description;
|
||||
return response;
|
||||
}
|
||||
|
||||
static inline nlohmann::json& request_body(nlohmann::json& path_operation)
|
||||
{
|
||||
auto& request_body = access::get_object(path_operation, "requestBody");
|
||||
access::get_object(request_body, "content");
|
||||
return request_body;
|
||||
}
|
||||
|
||||
static inline nlohmann::json& media_type(
|
||||
nlohmann::json& j, const std::string& mt)
|
||||
{
|
||||
auto& content = access::get_object(j, "content");
|
||||
return access::get_object(content, mt);
|
||||
}
|
||||
|
||||
static inline nlohmann::json& schema(nlohmann::json& media_type_object)
|
||||
{
|
||||
return access::get_object(media_type_object, "schema");
|
||||
}
|
||||
|
||||
//
|
||||
// Helper functions for auto-inserting schema into components
|
||||
//
|
||||
|
||||
static inline nlohmann::json components_ref_object(
|
||||
const std::string& element_name)
|
||||
{
|
||||
auto schema_ref_object = nlohmann::json::object();
|
||||
schema_ref_object["$ref"] =
|
||||
fmt::format("#/components/schemas/{}", element_name);
|
||||
return schema_ref_object;
|
||||
}
|
||||
|
||||
// Returns a ref object pointing to the item inserted into the components
|
||||
static inline nlohmann::json add_schema_to_components(
|
||||
nlohmann::json& document,
|
||||
const std::string& element_name,
|
||||
const nlohmann::json& schema_)
|
||||
{
|
||||
const auto name = remove_invalid_chars(element_name);
|
||||
|
||||
auto& components = access::get_object(document, "components");
|
||||
auto& schemas = access::get_object(components, "schemas");
|
||||
|
||||
const auto schema_it = schemas.find(name);
|
||||
if (schema_it != schemas.end())
|
||||
{
|
||||
// Check that the existing schema matches the new one being added with
|
||||
// the same name
|
||||
const auto& existing_schema = schema_it.value();
|
||||
if (schema_ != existing_schema)
|
||||
{
|
||||
throw std::logic_error(fmt::format(
|
||||
"Adding schema with name '{}'. Does not match previous schema "
|
||||
"registered with this name: {} vs {}",
|
||||
name,
|
||||
schema_.dump(),
|
||||
existing_schema.dump()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
schemas.emplace(name, schema_);
|
||||
}
|
||||
|
||||
return components_ref_object(name);
|
||||
}
|
||||
|
||||
struct SchemaHelper
|
||||
{
|
||||
nlohmann::json& document;
|
||||
|
||||
template <typename T>
|
||||
nlohmann::json add_schema_component()
|
||||
{
|
||||
nlohmann::json schema;
|
||||
if constexpr (nonstd::is_specialization<T, std::optional>::value)
|
||||
{
|
||||
return add_schema_component<typename T::value_type>();
|
||||
}
|
||||
else if constexpr (nonstd::is_specialization<T, std::vector>::value)
|
||||
{
|
||||
schema["type"] = "array";
|
||||
schema["items"] = add_schema_component<typename T::value_type>();
|
||||
|
||||
return add_schema_to_components(
|
||||
document, ds::json::schema_name<T>(), schema);
|
||||
}
|
||||
else if constexpr (
|
||||
nonstd::is_specialization<T, std::map>::value ||
|
||||
nonstd::is_specialization<T, std::unordered_map>::value)
|
||||
{
|
||||
// Nlohmann serialises maps to an array of (K, V) pairs
|
||||
if (std::is_same<typename T::key_type, std::string>::value)
|
||||
{
|
||||
// ...unless the keys are strings!
|
||||
schema["type"] = "object";
|
||||
schema["additionalProperties"] =
|
||||
add_schema_component<typename T::mapped_type>();
|
||||
}
|
||||
else
|
||||
{
|
||||
schema["type"] = "array";
|
||||
auto items = nlohmann::json::object();
|
||||
{
|
||||
items["type"] = "array";
|
||||
|
||||
auto sub_items = nlohmann::json::array();
|
||||
// NB: OpenAPI doesn't like this tuple for "items", even though
|
||||
// its valid JSON schema. May need to switch this to oneOf to
|
||||
// satisfy some validators
|
||||
sub_items.push_back(add_schema_component<typename T::key_type>());
|
||||
sub_items.push_back(
|
||||
add_schema_component<typename T::mapped_type>());
|
||||
items["items"] = sub_items;
|
||||
}
|
||||
schema["items"] = items;
|
||||
}
|
||||
return add_schema_to_components(
|
||||
document, ds::json::schema_name<T>(), schema);
|
||||
}
|
||||
else if constexpr (nonstd::is_specialization<T, std::pair>::value)
|
||||
{
|
||||
schema["type"] = "array";
|
||||
auto items = nlohmann::json::array();
|
||||
items.push_back(add_schema_component<typename T::first_type>());
|
||||
items.push_back(add_schema_component<typename T::second_type>());
|
||||
schema["items"] = items;
|
||||
return add_schema_to_components(
|
||||
document, ds::json::schema_name<T>(), schema);
|
||||
}
|
||||
else if constexpr (
|
||||
std::is_same<T, std::string>::value || std::is_arithmetic_v<T> ||
|
||||
std::is_same<T, nlohmann::json>::value ||
|
||||
std::is_same<T, ds::json::JsonSchema>::value)
|
||||
{
|
||||
ds::json::fill_schema<T>(schema);
|
||||
return add_schema_to_components(
|
||||
document, ds::json::schema_name<T>(), schema);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto name = remove_invalid_chars(ds::json::schema_name<T>());
|
||||
|
||||
auto& components = access::get_object(document, "components");
|
||||
auto& schemas = access::get_object(components, "schemas");
|
||||
|
||||
const auto ib = schemas.emplace(name, nlohmann::json::object());
|
||||
if (ib.second)
|
||||
{
|
||||
auto& j = ib.first.value();
|
||||
|
||||
// Use argument-dependent-lookup to call correct functions
|
||||
T t;
|
||||
if constexpr (std::is_enum<T>::value)
|
||||
{
|
||||
fill_enum_schema(j, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
add_schema_components(*this, j, t);
|
||||
}
|
||||
}
|
||||
|
||||
return components_ref_object(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static inline void add_request_body_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
http_method verb,
|
||||
const std::string& content_type,
|
||||
const std::string& schema_name,
|
||||
const nlohmann::json& schema_)
|
||||
{
|
||||
auto& rb = request_body(path_operation(path(document, uri), verb));
|
||||
rb["description"] = "Auto-generated request body schema";
|
||||
|
||||
schema(media_type(rb, content_type)) =
|
||||
add_schema_to_components(document, schema_name, schema_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void add_request_body_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
http_method verb,
|
||||
const std::string& content_type)
|
||||
{
|
||||
auto& rb = request_body(path_operation(path(document, uri), verb));
|
||||
rb["description"] = "Auto-generated request body schema";
|
||||
|
||||
SchemaHelper sh{document};
|
||||
const auto schema_comp = sh.add_schema_component<T>();
|
||||
if (schema_comp != nullptr)
|
||||
{
|
||||
schema(media_type(rb, content_type)) = sh.add_schema_component<T>();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_path_parameter_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
const nlohmann::json& param)
|
||||
{
|
||||
auto& params = parameters(path(document, uri));
|
||||
params.push_back(param);
|
||||
}
|
||||
|
||||
static inline void add_request_parameter_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
http_method verb,
|
||||
const nlohmann::json& param)
|
||||
{
|
||||
auto& params = parameters(path_operation(path(document, uri), verb));
|
||||
params.push_back(param);
|
||||
}
|
||||
|
||||
static inline void add_response_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
http_method verb,
|
||||
http_status status,
|
||||
const std::string& content_type,
|
||||
const std::string& schema_name,
|
||||
const nlohmann::json& schema_)
|
||||
{
|
||||
auto& r = response(path_operation(path(document, uri), verb), status);
|
||||
|
||||
schema(media_type(r, content_type)) =
|
||||
add_schema_to_components(document, schema_name, schema_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void add_response_schema(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
http_method verb,
|
||||
http_status status,
|
||||
const std::string& content_type)
|
||||
{
|
||||
auto& r = response(path_operation(path(document, uri), verb), status);
|
||||
|
||||
SchemaHelper sh{document};
|
||||
const auto schema_comp = sh.add_schema_component<T>();
|
||||
if (schema_comp != nullptr)
|
||||
{
|
||||
schema(media_type(r, content_type)) = sh.add_schema_component<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
|
@ -0,0 +1,214 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#include "ds/openapi.h"
|
||||
|
||||
#include "http/http_consts.h"
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace ds;
|
||||
|
||||
void print_doc(const std::string& title, const nlohmann::json& doc)
|
||||
{
|
||||
std::cout << title << std::endl;
|
||||
std::cout << doc.dump(2) << std::endl;
|
||||
}
|
||||
|
||||
#define REQUIRE_ELEMENT(j, name, type_fn) \
|
||||
{ \
|
||||
const auto name##_it = j.find(#name); \
|
||||
REQUIRE(name##_it != j.end()); \
|
||||
REQUIRE(name##_it->type_fn()); \
|
||||
}
|
||||
|
||||
static constexpr auto server_url = "https://not.a.real.server.com/testing_only";
|
||||
|
||||
// This is only a very basic check - assume full validation is done by external
|
||||
// validator
|
||||
void required_doc_elements(const nlohmann::json& j)
|
||||
{
|
||||
REQUIRE_ELEMENT(j, openapi, is_string);
|
||||
REQUIRE_ELEMENT(j, info, is_object);
|
||||
REQUIRE_ELEMENT(j, paths, is_object);
|
||||
}
|
||||
|
||||
// TEST_CASE("Required elements")
|
||||
// {
|
||||
// openapi::Document doc;
|
||||
|
||||
// const nlohmann::json j = doc;
|
||||
// required_doc_elements(j);
|
||||
// }
|
||||
|
||||
TEST_CASE("Manual construction")
|
||||
{
|
||||
auto doc = openapi::create_document(
|
||||
"Test generated API",
|
||||
"Some longer description enhanced with **Markdown**",
|
||||
"0.1.42");
|
||||
|
||||
openapi::server(doc, server_url);
|
||||
|
||||
const auto string_schema = nlohmann::json{{"type", "string"}};
|
||||
|
||||
auto& foo = openapi::path(doc, "/users/foo");
|
||||
auto& foo_post = openapi::path_operation(foo, HTTP_POST);
|
||||
auto& foo_post_request = openapi::request_body(foo_post);
|
||||
auto& foo_post_request_json = openapi::media_type(
|
||||
foo_post_request, http::headervalues::contenttype::JSON);
|
||||
auto& foo_post_request_json_schema = openapi::schema(foo_post_request_json);
|
||||
foo_post_request_json_schema = string_schema;
|
||||
|
||||
auto& foo_post_response_ok = openapi::response(
|
||||
foo_post, HTTP_STATUS_OK, "Indicates that everything went ok");
|
||||
auto& foo_post_response_ok_json = openapi::media_type(
|
||||
foo_post_response_ok, http::headervalues::contenttype::JSON);
|
||||
auto& foo_post_response_ok_json_schema =
|
||||
openapi::schema(foo_post_response_ok_json);
|
||||
foo_post_response_ok_json_schema = string_schema;
|
||||
|
||||
required_doc_elements(doc);
|
||||
|
||||
const auto& info_element = doc["info"];
|
||||
REQUIRE_ELEMENT(info_element, title, is_string);
|
||||
REQUIRE_ELEMENT(info_element, description, is_string);
|
||||
REQUIRE_ELEMENT(info_element, version, is_string);
|
||||
|
||||
REQUIRE_ELEMENT(doc, servers, is_array);
|
||||
const auto& servers_element = doc["servers"];
|
||||
REQUIRE(servers_element.size() == 1);
|
||||
const auto& first_server = servers_element[0];
|
||||
REQUIRE_ELEMENT(first_server, url, is_string);
|
||||
}
|
||||
|
||||
struct Foo
|
||||
{
|
||||
size_t n;
|
||||
std::string s;
|
||||
};
|
||||
DECLARE_JSON_TYPE(Foo);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Foo, n, s);
|
||||
|
||||
TEST_CASE("Simple custom types")
|
||||
{
|
||||
auto doc = openapi::create_document(
|
||||
"Test generated API",
|
||||
"Some longer description enhanced with **Markdown**",
|
||||
"0.1.42");
|
||||
|
||||
openapi::server(doc, server_url);
|
||||
|
||||
openapi::add_request_body_schema<Foo>(
|
||||
doc, "/app/foo", HTTP_POST, http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<size_t>(
|
||||
doc,
|
||||
"/app/foo",
|
||||
HTTP_POST,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<Foo>(
|
||||
doc,
|
||||
"/app/foo",
|
||||
HTTP_POST,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
|
||||
required_doc_elements(doc);
|
||||
}
|
||||
|
||||
struct Bar
|
||||
{
|
||||
std::string name;
|
||||
double f;
|
||||
};
|
||||
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Bar);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Bar, name);
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(Bar, f);
|
||||
|
||||
enum class Vehicle
|
||||
{
|
||||
Car,
|
||||
Pedalo,
|
||||
Submarine,
|
||||
};
|
||||
|
||||
DECLARE_JSON_ENUM(
|
||||
Vehicle,
|
||||
{{Vehicle::Car, "vroom vroom"},
|
||||
{Vehicle::Pedalo, "splash splash"},
|
||||
{Vehicle::Submarine, "glug glug"}});
|
||||
|
||||
struct Baz : public Bar
|
||||
{
|
||||
uint16_t n;
|
||||
double x;
|
||||
double y;
|
||||
Vehicle v;
|
||||
};
|
||||
DECLARE_JSON_TYPE_WITH_BASE_AND_OPTIONAL_FIELDS(Baz, Bar);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Baz, n, v);
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(Baz, x, y);
|
||||
|
||||
struct Buzz : public Baz
|
||||
{
|
||||
Foo required_and_only_in_c;
|
||||
uint16_t optional_and_only_in_c;
|
||||
};
|
||||
DECLARE_JSON_TYPE_WITH_BASE_AND_OPTIONAL_FIELDS(Buzz, Baz);
|
||||
DECLARE_JSON_REQUIRED_FIELDS_WITH_RENAMES(
|
||||
Buzz, required_and_only_in_c, RequiredJsonField);
|
||||
DECLARE_JSON_OPTIONAL_FIELDS_WITH_RENAMES(
|
||||
Buzz, optional_and_only_in_c, OptionalJsonField);
|
||||
|
||||
TEST_CASE("Complex custom types")
|
||||
{
|
||||
auto doc = openapi::create_document(
|
||||
"Test generated API",
|
||||
"Some longer description enhanced with **Markdown**",
|
||||
"0.1.42");
|
||||
|
||||
openapi::server(doc, server_url);
|
||||
|
||||
openapi::add_response_schema<std::vector<Foo>>(
|
||||
doc,
|
||||
"/app/foos",
|
||||
HTTP_GET,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<std::vector<std::vector<Foo>>>(
|
||||
doc,
|
||||
"/app/fooss",
|
||||
HTTP_GET,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<Bar>(
|
||||
doc,
|
||||
"/app/bar",
|
||||
HTTP_GET,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<Baz>(
|
||||
doc,
|
||||
"/app/baz",
|
||||
HTTP_GET,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<std::map<std::string, Buzz>>(
|
||||
doc,
|
||||
"/app/buzz",
|
||||
HTTP_GET,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
|
||||
openapi::add_request_body_schema<std::optional<Bar>>(
|
||||
doc, "/app/complex", HTTP_POST, http::headervalues::contenttype::JSON);
|
||||
openapi::add_response_schema<std::map<Baz, std::vector<Buzz>>>(
|
||||
doc,
|
||||
"/app/complex",
|
||||
HTTP_POST,
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
|
||||
required_doc_elements(doc);
|
||||
}
|
|
@ -35,6 +35,16 @@ namespace ccf
|
|||
RESTVerb(const http_method& hm) : verb(hm) {}
|
||||
RESTVerb(const ws::Verb& wv) : verb(wv) {}
|
||||
|
||||
std::optional<http_method> get_http_method() const
|
||||
{
|
||||
if (verb == ws::WEBSOCKET)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<http_method>(verb);
|
||||
}
|
||||
|
||||
const char* c_str() const
|
||||
{
|
||||
if (verb == ws::WEBSOCKET)
|
||||
|
|
|
@ -63,9 +63,7 @@ namespace http
|
|||
void set_header(std::string k, const std::string& v)
|
||||
{
|
||||
// Store all headers lower-cased to simplify case-insensitive lookup
|
||||
std::transform(k.begin(), k.end(), k.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
nonstd::to_lower(k);
|
||||
headers[k] = v;
|
||||
}
|
||||
|
||||
|
|
|
@ -279,9 +279,7 @@ namespace http
|
|||
// HTTP headers are stored lowercase as it is easier to verify HTTP
|
||||
// signatures later on
|
||||
auto f = std::string(at, length);
|
||||
std::transform(f.begin(), f.end(), f.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
nonstd::to_lower(f);
|
||||
partial_parsed_header.first.append(f);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,7 @@ namespace http
|
|||
if (f == auth::SIGN_HEADER_REQUEST_TARGET)
|
||||
{
|
||||
// Store verb as lowercase
|
||||
std::transform(
|
||||
verb.begin(), verb.end(), verb.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
nonstd::to_lower(verb);
|
||||
value = fmt::format("{} {}", verb, path);
|
||||
if (!query.empty())
|
||||
{
|
||||
|
|
|
@ -24,9 +24,7 @@ std::vector<uint8_t> s_to_v(char const* s)
|
|||
|
||||
std::string to_lowercase(std::string s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
nonstd::to_lower(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,18 +132,9 @@ namespace ccf
|
|||
using Out = CallerInfo;
|
||||
};
|
||||
|
||||
struct ListMethods
|
||||
struct GetAPI
|
||||
{
|
||||
struct Endpoint
|
||||
{
|
||||
std::string verb;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct Out
|
||||
{
|
||||
std::vector<Endpoint> endpoints;
|
||||
};
|
||||
using Out = nlohmann::json;
|
||||
};
|
||||
|
||||
struct EndpointMetrics
|
||||
|
|
|
@ -27,8 +27,10 @@ namespace ccf
|
|||
|
||||
public:
|
||||
CommonEndpointRegistry(
|
||||
kv::Store& store, const std::string& certs_table_name = "") :
|
||||
EndpointRegistry(store, certs_table_name),
|
||||
const std::string& method_prefix_,
|
||||
kv::Store& store,
|
||||
const std::string& certs_table_name = "") :
|
||||
EndpointRegistry(method_prefix_, store, certs_table_name),
|
||||
nodes(store.get<Nodes>(Tables::NODES)),
|
||||
node_code_ids(store.get<CodeIDs>(Tables::NODE_CODE_IDS)),
|
||||
tables(&store)
|
||||
|
@ -221,14 +223,16 @@ namespace ccf
|
|||
.set_auto_schema<GetNodesByRPCAddress::In, GetNodesByRPCAddress::Out>()
|
||||
.install();
|
||||
|
||||
auto list_methods_fn = [this](kv::Tx& tx, nlohmann::json&&) {
|
||||
ListMethods::Out out;
|
||||
list_methods(tx, out);
|
||||
|
||||
return make_success(out);
|
||||
auto openapi = [this](kv::Tx& tx, nlohmann::json&&) {
|
||||
auto document = ds::openapi::create_document(
|
||||
openapi_info.title,
|
||||
openapi_info.description,
|
||||
openapi_info.document_version);
|
||||
build_api(document, tx);
|
||||
return make_success(document);
|
||||
};
|
||||
make_endpoint("api", HTTP_GET, json_adapter(list_methods_fn))
|
||||
.set_auto_schema<void, ListMethods::Out>()
|
||||
make_endpoint("api", HTTP_GET, json_adapter(openapi))
|
||||
.set_auto_schema<void, GetAPI::Out>()
|
||||
.install();
|
||||
|
||||
auto endpoint_metrics_fn = [this](kv::Tx& tx, nlohmann::json&&) {
|
||||
|
@ -242,43 +246,10 @@ namespace ccf
|
|||
.set_auto_schema<void, EndpointMetrics::Out>()
|
||||
.install();
|
||||
|
||||
auto get_schema = [this](auto&, nlohmann::json&& params) {
|
||||
auto get_schema = [this](kv::Tx& tx, nlohmann::json&& params) {
|
||||
const auto in = params.get<GetSchema::In>();
|
||||
|
||||
auto j = nlohmann::json::object();
|
||||
|
||||
const auto it = fully_qualified_endpoints.find(in.method);
|
||||
if (it != fully_qualified_endpoints.end())
|
||||
{
|
||||
for (const auto& [verb, endpoint] : it->second)
|
||||
{
|
||||
std::string verb_name = verb.c_str();
|
||||
std::transform(
|
||||
verb_name.begin(),
|
||||
verb_name.end(),
|
||||
verb_name.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
j[verb_name] =
|
||||
GetSchema::Out{endpoint.params_schema, endpoint.result_schema};
|
||||
}
|
||||
}
|
||||
|
||||
const auto templated_it = templated_endpoints.find(in.method);
|
||||
if (templated_it != templated_endpoints.end())
|
||||
{
|
||||
for (const auto& [verb, endpoint] : templated_it->second)
|
||||
{
|
||||
std::string verb_name = verb.c_str();
|
||||
std::transform(
|
||||
verb_name.begin(),
|
||||
verb_name.end(),
|
||||
verb_name.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
j[verb_name] =
|
||||
GetSchema::Out{endpoint.params_schema, endpoint.result_schema};
|
||||
}
|
||||
}
|
||||
|
||||
auto j = get_endpoint_schema(tx, in);
|
||||
if (j.empty())
|
||||
{
|
||||
return make_error(
|
||||
|
@ -288,8 +259,7 @@ namespace ccf
|
|||
|
||||
return make_success(j);
|
||||
};
|
||||
make_command_endpoint(
|
||||
"api/schema", HTTP_GET, json_command_adapter(get_schema))
|
||||
make_endpoint("api/schema", HTTP_GET, json_adapter(get_schema))
|
||||
.set_auto_schema<GetSchema>()
|
||||
.install();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "ds/json_schema.h"
|
||||
#include "ds/openapi.h"
|
||||
#include "enclave/rpc_context.h"
|
||||
#include "http/http_consts.h"
|
||||
#include "http/ws_consts.h"
|
||||
|
@ -65,6 +66,15 @@ namespace ccf
|
|||
Write
|
||||
};
|
||||
|
||||
const std::string method_prefix;
|
||||
|
||||
struct OpenApiInfo
|
||||
{
|
||||
std::string title = "Empty title";
|
||||
std::string description = "Empty description";
|
||||
std::string document_version = "0.0.1";
|
||||
} openapi_info;
|
||||
|
||||
struct Metrics
|
||||
{
|
||||
size_t calls = 0;
|
||||
|
@ -72,6 +82,10 @@ namespace ccf
|
|||
size_t failures = 0;
|
||||
};
|
||||
|
||||
struct Endpoint;
|
||||
using SchemaBuilderFn =
|
||||
std::function<void(nlohmann::json&, const Endpoint&)>;
|
||||
|
||||
/** An Endpoint represents a user-defined resource that can be invoked by
|
||||
* authorised users via HTTP requests, over TLS. An Endpoint is accessible
|
||||
* at a specific verb and URI, e.g. POST /app/accounts or GET /app/records.
|
||||
|
@ -88,6 +102,9 @@ namespace ccf
|
|||
EndpointFunction func;
|
||||
EndpointRegistry* registry = nullptr;
|
||||
Metrics metrics = {};
|
||||
|
||||
std::vector<SchemaBuilderFn> schema_builders = {};
|
||||
|
||||
nlohmann::json params_schema = nullptr;
|
||||
|
||||
/** Sets the JSON schema that the request parameters must comply with.
|
||||
|
@ -98,6 +115,35 @@ namespace ccf
|
|||
Endpoint& set_params_schema(const nlohmann::json& j)
|
||||
{
|
||||
params_schema = j;
|
||||
|
||||
schema_builders.push_back([](
|
||||
nlohmann::json& document,
|
||||
const Endpoint& endpoint) {
|
||||
const auto http_verb = endpoint.verb.get_http_method();
|
||||
if (!http_verb.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace ds::openapi;
|
||||
|
||||
if (http_verb.value() == HTTP_GET || http_verb.value() == HTTP_DELETE)
|
||||
{
|
||||
add_query_parameters(
|
||||
document,
|
||||
endpoint.method,
|
||||
endpoint.params_schema,
|
||||
http_verb.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& rb = request_body(path_operation(
|
||||
ds::openapi::path(document, endpoint.method), http_verb.value()));
|
||||
schema(media_type(rb, http::headervalues::contenttype::JSON)) =
|
||||
endpoint.params_schema;
|
||||
}
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -111,6 +157,29 @@ namespace ccf
|
|||
Endpoint& set_result_schema(const nlohmann::json& j)
|
||||
{
|
||||
result_schema = j;
|
||||
|
||||
schema_builders.push_back(
|
||||
[j](nlohmann::json& document, const Endpoint& endpoint) {
|
||||
const auto http_verb = endpoint.verb.get_http_method();
|
||||
if (!http_verb.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace ds::openapi;
|
||||
auto& r = response(
|
||||
path_operation(
|
||||
ds::openapi::path(document, endpoint.method),
|
||||
http_verb.value()),
|
||||
HTTP_STATUS_OK);
|
||||
|
||||
if (endpoint.result_schema != nullptr)
|
||||
{
|
||||
schema(media_type(r, http::headervalues::contenttype::JSON)) =
|
||||
endpoint.result_schema;
|
||||
}
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -133,6 +202,35 @@ namespace ccf
|
|||
if constexpr (!std::is_same_v<In, void>)
|
||||
{
|
||||
params_schema = ds::json::build_schema<In>(method + "/params");
|
||||
|
||||
schema_builders.push_back(
|
||||
[](nlohmann::json& document, const Endpoint& endpoint) {
|
||||
const auto http_verb = endpoint.verb.get_http_method();
|
||||
if (!http_verb.has_value())
|
||||
{
|
||||
// Non-HTTP (ie WebSockets) endpoints are not documented
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
http_verb.value() == HTTP_GET ||
|
||||
http_verb.value() == HTTP_DELETE)
|
||||
{
|
||||
add_query_parameters(
|
||||
document,
|
||||
endpoint.method,
|
||||
endpoint.params_schema,
|
||||
http_verb.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
ds::openapi::add_request_body_schema<In>(
|
||||
document,
|
||||
endpoint.method,
|
||||
http_verb.value(),
|
||||
http::headervalues::contenttype::JSON);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -142,6 +240,22 @@ namespace ccf
|
|||
if constexpr (!std::is_same_v<Out, void>)
|
||||
{
|
||||
result_schema = ds::json::build_schema<Out>(method + "/result");
|
||||
|
||||
schema_builders.push_back(
|
||||
[](nlohmann::json& document, const Endpoint& endpoint) {
|
||||
const auto http_verb = endpoint.verb.get_http_method();
|
||||
if (!http_verb.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ds::openapi::add_response_schema<Out>(
|
||||
document,
|
||||
endpoint.method,
|
||||
http_verb.value(),
|
||||
HTTP_STATUS_OK,
|
||||
http::headervalues::contenttype::JSON);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -328,9 +442,38 @@ namespace ccf
|
|||
return templated;
|
||||
}
|
||||
|
||||
static void add_query_parameters(
|
||||
nlohmann::json& document,
|
||||
const std::string& uri,
|
||||
const nlohmann::json& schema,
|
||||
http_method verb)
|
||||
{
|
||||
if (schema["type"] != "object")
|
||||
{
|
||||
throw std::logic_error(
|
||||
fmt::format("Unexpected params schema type: {}", schema.dump()));
|
||||
}
|
||||
|
||||
const auto& required_parameters = schema["required"];
|
||||
for (const auto& [name, schema] : schema["properties"].items())
|
||||
{
|
||||
auto parameter = nlohmann::json::object();
|
||||
parameter["name"] = name;
|
||||
parameter["in"] = "query";
|
||||
parameter["required"] =
|
||||
required_parameters.find(name) != required_parameters.end();
|
||||
parameter["schema"] = schema;
|
||||
ds::openapi::add_request_parameter_schema(
|
||||
document, uri, verb, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
EndpointRegistry(
|
||||
kv::Store& tables, const std::string& certs_table_name = "")
|
||||
const std::string& method_prefix_,
|
||||
kv::Store& tables,
|
||||
const std::string& certs_table_name = "") :
|
||||
method_prefix(method_prefix_)
|
||||
{
|
||||
if (!certs_table_name.empty())
|
||||
{
|
||||
|
@ -436,19 +579,30 @@ namespace ccf
|
|||
return default_endpoint.value();
|
||||
}
|
||||
|
||||
/** Populate out with all supported methods
|
||||
*
|
||||
* This is virtual since the default endpoint may do its own dispatch
|
||||
* internally, so derived implementations must be able to populate the list
|
||||
* with the supported methods however it constructs them.
|
||||
*/
|
||||
virtual void list_methods(kv::Tx&, ListMethods::Out& out)
|
||||
static void add_endpoint_to_api_document(
|
||||
nlohmann::json& document, const Endpoint& endpoint)
|
||||
{
|
||||
for (const auto& builder_fn : endpoint.schema_builders)
|
||||
{
|
||||
builder_fn(document, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
/** Populate document with all supported methods
|
||||
*
|
||||
* This is virtual since derived classes may do their own dispatch
|
||||
* internally, so must be able to populate the document
|
||||
* with the supported endpoints however it defines them.
|
||||
*/
|
||||
virtual void build_api(nlohmann::json& document, kv::Tx&)
|
||||
{
|
||||
ds::openapi::server(document, fmt::format("/{}", method_prefix));
|
||||
|
||||
for (const auto& [path, verb_endpoints] : fully_qualified_endpoints)
|
||||
{
|
||||
for (const auto& [verb, endpoint] : verb_endpoints)
|
||||
{
|
||||
out.endpoints.push_back({verb.c_str(), path});
|
||||
add_endpoint_to_api_document(document, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -456,11 +610,53 @@ namespace ccf
|
|||
{
|
||||
for (const auto& [verb, endpoint] : verb_endpoints)
|
||||
{
|
||||
out.endpoints.push_back({verb.c_str(), path});
|
||||
add_endpoint_to_api_document(document, endpoint);
|
||||
|
||||
for (const auto& name : endpoint.template_component_names)
|
||||
{
|
||||
auto parameter = nlohmann::json::object();
|
||||
parameter["name"] = name;
|
||||
parameter["in"] = "path";
|
||||
parameter["required"] = true;
|
||||
parameter["schema"] = {{"type", "string"}};
|
||||
ds::openapi::add_path_parameter_schema(
|
||||
document, endpoint.method, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual nlohmann::json get_endpoint_schema(kv::Tx&, const GetSchema::In& in)
|
||||
{
|
||||
auto j = nlohmann::json::object();
|
||||
|
||||
const auto it = fully_qualified_endpoints.find(in.method);
|
||||
if (it != fully_qualified_endpoints.end())
|
||||
{
|
||||
for (const auto& [verb, endpoint] : it->second)
|
||||
{
|
||||
std::string verb_name = verb.c_str();
|
||||
nonstd::to_lower(verb_name);
|
||||
j[verb_name] =
|
||||
GetSchema::Out{endpoint.params_schema, endpoint.result_schema};
|
||||
}
|
||||
}
|
||||
|
||||
const auto templated_it = templated_endpoints.find(in.method);
|
||||
if (templated_it != templated_endpoints.end())
|
||||
{
|
||||
for (const auto& [verb, endpoint] : templated_it->second)
|
||||
{
|
||||
std::string verb_name = verb.c_str();
|
||||
nonstd::to_lower(verb_name);
|
||||
j[verb_name] =
|
||||
GetSchema::Out{endpoint.params_schema, endpoint.result_schema};
|
||||
}
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
virtual void endpoint_metrics(kv::Tx&, EndpointMetrics::Out& out)
|
||||
{
|
||||
for (const auto& [path, verb_endpoints] : fully_qualified_endpoints)
|
||||
|
|
|
@ -752,12 +752,20 @@ namespace ccf
|
|||
NetworkTables& network,
|
||||
AbstractNodeState& node,
|
||||
ShareManager& share_manager) :
|
||||
CommonEndpointRegistry(*network.tables, Tables::MEMBER_CERT_DERS),
|
||||
CommonEndpointRegistry(
|
||||
get_actor_prefix(ActorsType::members),
|
||||
*network.tables,
|
||||
Tables::MEMBER_CERT_DERS),
|
||||
network(network),
|
||||
node(node),
|
||||
share_manager(share_manager),
|
||||
tsr(network)
|
||||
{}
|
||||
{
|
||||
openapi_info.title = "CCF Governance API";
|
||||
openapi_info.description =
|
||||
"This API is used to submit and query proposals which affect CCF's "
|
||||
"public governance tables.";
|
||||
}
|
||||
|
||||
void init_handlers(kv::Store& tables_) override
|
||||
{
|
||||
|
|
|
@ -136,10 +136,16 @@ namespace ccf
|
|||
|
||||
public:
|
||||
NodeEndpoints(NetworkState& network, AbstractNodeState& node) :
|
||||
CommonEndpointRegistry(*network.tables),
|
||||
CommonEndpointRegistry(
|
||||
get_actor_prefix(ActorsType::nodes), *network.tables),
|
||||
network(network),
|
||||
node(node)
|
||||
{}
|
||||
{
|
||||
openapi_info.title = "CCF Public Node API";
|
||||
openapi_info.description =
|
||||
"This API provides public, uncredentialed access to service and node "
|
||||
"state.";
|
||||
}
|
||||
|
||||
void init_handlers(kv::Store& tables_) override
|
||||
{
|
||||
|
|
|
@ -112,11 +112,6 @@ namespace ccf
|
|||
DECLARE_JSON_TYPE(GetUserId::In)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(GetUserId::In, cert)
|
||||
|
||||
DECLARE_JSON_TYPE(ListMethods::Endpoint)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(ListMethods::Endpoint, verb, path)
|
||||
DECLARE_JSON_TYPE(ListMethods::Out)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(ListMethods::Out, endpoints)
|
||||
|
||||
DECLARE_JSON_TYPE(EndpointMetrics::Metric)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(EndpointMetrics::Metric, calls, errors, failures)
|
||||
DECLARE_JSON_TYPE(EndpointMetrics::Out)
|
||||
|
|
|
@ -240,7 +240,7 @@ class TestNoCertsFrontend : public RpcFrontend
|
|||
public:
|
||||
TestNoCertsFrontend(kv::Store& tables) :
|
||||
RpcFrontend(tables, endpoints),
|
||||
endpoints(tables)
|
||||
endpoints("test", tables)
|
||||
{
|
||||
open();
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ namespace ccf
|
|||
h,
|
||||
tables.get<ClientSignatures>(Tables::USER_CLIENT_SIGNATURES)),
|
||||
users(tables.get<Users>(Tables::USERS))
|
||||
{}
|
||||
{
|
||||
h.openapi_info.title = "CCF Application API";
|
||||
}
|
||||
|
||||
void open() override
|
||||
{
|
||||
|
@ -81,11 +83,15 @@ namespace ccf
|
|||
{
|
||||
public:
|
||||
UserEndpointRegistry(kv::Store& store) :
|
||||
CommonEndpointRegistry(store, Tables::USER_CERT_DERS)
|
||||
CommonEndpointRegistry(
|
||||
get_actor_prefix(ActorsType::users), store, Tables::USER_CERT_DERS)
|
||||
{}
|
||||
|
||||
UserEndpointRegistry(NetworkTables& network) :
|
||||
CommonEndpointRegistry(*network.tables, Tables::USER_CERT_DERS)
|
||||
CommonEndpointRegistry(
|
||||
get_actor_prefix(ActorsType::users),
|
||||
*network.tables,
|
||||
Tables::USER_CERT_DERS)
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,4 +4,5 @@ loguru
|
|||
coincurve
|
||||
psutil
|
||||
cimetrics>=0.2.1
|
||||
pynacl
|
||||
pynacl
|
||||
openapi-spec-validator
|
|
@ -8,6 +8,8 @@ import infra.network
|
|||
import infra.proc
|
||||
import infra.e2e_args
|
||||
import infra.checker
|
||||
import openapi_spec_validator
|
||||
|
||||
|
||||
from loguru import logger as LOG
|
||||
|
||||
|
@ -29,18 +31,26 @@ def run(args):
|
|||
for filename in filenames
|
||||
)
|
||||
|
||||
documents_valid = True
|
||||
|
||||
all_methods = []
|
||||
|
||||
def fetch_schema(client, prefix):
|
||||
list_response = client.get(f"/{prefix}/api")
|
||||
api_response = client.get(f"/{prefix}/api")
|
||||
check(
|
||||
list_response, error=lambda status, msg: status == http.HTTPStatus.OK.value
|
||||
api_response, error=lambda status, msg: status == http.HTTPStatus.OK.value
|
||||
)
|
||||
methods = list_response.body.json()["endpoints"]
|
||||
all_methods.extend([m["path"] for m in methods])
|
||||
|
||||
for method in [m["path"] for m in methods]:
|
||||
response_body = api_response.body.json()
|
||||
paths = response_body["paths"]
|
||||
all_methods.extend(paths.keys())
|
||||
|
||||
# Fetch the schema of each method
|
||||
for method, _ in paths.items():
|
||||
schema_found = False
|
||||
expected_method_prefix = "/"
|
||||
if method.startswith(expected_method_prefix):
|
||||
method = method[len(expected_method_prefix) :]
|
||||
schema_response = client.get(f'/{prefix}/api/schema?method="{method}"')
|
||||
check(
|
||||
schema_response,
|
||||
|
@ -84,6 +94,35 @@ def run(args):
|
|||
else:
|
||||
methods_without_schema.add(method)
|
||||
|
||||
formatted_schema = json.dumps(response_body, indent=2)
|
||||
openapi_target_file = os.path.join(args.schema_dir, f"{prefix}_openapi.json")
|
||||
|
||||
try:
|
||||
old_schema.remove(openapi_target_file)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
with open(openapi_target_file, "a+") as f:
|
||||
f.seek(0)
|
||||
previous = f.read()
|
||||
if previous != formatted_schema:
|
||||
LOG.debug("Writing schema to {}".format(openapi_target_file))
|
||||
f.truncate(0)
|
||||
f.seek(0)
|
||||
f.write(formatted_schema)
|
||||
changed_files.append(openapi_target_file)
|
||||
else:
|
||||
LOG.debug("Schema matches in {}".format(openapi_target_file))
|
||||
|
||||
try:
|
||||
openapi_spec_validator.validate_spec(response_body)
|
||||
except Exception as e:
|
||||
LOG.error(f"Validation of {prefix} schema failed")
|
||||
LOG.error(e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
with infra.network.network(
|
||||
hosts, args.binary_dir, args.debug_nodes, args.perf_nodes
|
||||
) as network:
|
||||
|
@ -94,20 +133,18 @@ def run(args):
|
|||
|
||||
with primary.client("user0") as user_client:
|
||||
LOG.info("user frontend")
|
||||
fetch_schema(user_client, "app")
|
||||
if not fetch_schema(user_client, "app"):
|
||||
documents_valid = False
|
||||
|
||||
with primary.client() as node_client:
|
||||
LOG.info("node frontend")
|
||||
fetch_schema(node_client, "node")
|
||||
if not fetch_schema(node_client, "node"):
|
||||
documents_valid = False
|
||||
|
||||
with primary.client("member0") as member_client:
|
||||
LOG.info("member frontend")
|
||||
fetch_schema(member_client, "gov")
|
||||
|
||||
if len(methods_without_schema) > 0:
|
||||
LOG.info("The following methods have no schema:")
|
||||
for m in sorted(methods_without_schema):
|
||||
LOG.info(" " + m)
|
||||
if not fetch_schema(member_client, "gov"):
|
||||
documents_valid = False
|
||||
|
||||
made_changes = False
|
||||
|
||||
|
@ -134,7 +171,7 @@ def run(args):
|
|||
for method in sorted(set(all_methods)):
|
||||
LOG.info(f" {method}")
|
||||
|
||||
if made_changes:
|
||||
if made_changes or not documents_valid:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
|
|
@ -48,12 +48,20 @@ def ensure_reqs(check_reqs):
|
|||
|
||||
|
||||
def supports_methods(*methods):
|
||||
def remove_prefix(s, prefix):
|
||||
if s.startswith(prefix):
|
||||
return s[len(prefix) :]
|
||||
return s
|
||||
|
||||
def check(network, args, *nargs, **kwargs):
|
||||
primary, _ = network.find_primary()
|
||||
with primary.client("user0") as c:
|
||||
response = c.get("/app/api")
|
||||
supported_methods = response.body.json()["endpoints"]
|
||||
missing = {*methods}.difference([sm["path"] for sm in supported_methods])
|
||||
supported_methods = response.body.json()["paths"]
|
||||
LOG.warning(f"Supported methods are: {supported_methods.keys()}")
|
||||
missing = {*methods}.difference(
|
||||
[remove_prefix(key, "/") for key in supported_methods.keys()]
|
||||
)
|
||||
if missing:
|
||||
concat = ", ".join(missing)
|
||||
raise TestRequirementsNotMet(f"Missing required methods: {concat}")
|
||||
|
|
Загрузка…
Ссылка в новой задаче