From ed29f84c77a613034558208f322fe20ba30d8102 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 23 Dec 2024 14:12:20 -0600 Subject: [PATCH] rpc: remove logic for generating openapi spec Since we'll be leveraging gRPC + protobuf instead of a handrolled REST api we no longer need to maintain the infra for generating an openapi scpec. In the future we can explore generating an openapi spec from the proto files themselves as is used in grpc-gateway. --- Cargo.lock | 61 +- crates/sui-rpc-api/Cargo.toml | 4 - crates/sui-rpc-api/openapi/elements.html | 21 - crates/sui-rpc-api/openapi/openapi.json | 664 ----------------- crates/sui-rpc-api/openapi/swagger.html | 22 - crates/sui-rpc-api/src/lib.rs | 2 +- crates/sui-rpc-api/src/rest/accounts.rs | 21 +- crates/sui-rpc-api/src/rest/checkpoints.rs | 63 +- crates/sui-rpc-api/src/rest/coins.rs | 22 +- crates/sui-rpc-api/src/rest/committee.rs | 40 +- crates/sui-rpc-api/src/rest/health.rs | 30 +- crates/sui-rpc-api/src/rest/info.rs | 28 +- crates/sui-rpc-api/src/rest/mod.rs | 96 +-- crates/sui-rpc-api/src/rest/objects.rs | 72 +- crates/sui-rpc-api/src/rest/openapi.rs | 685 ------------------ crates/sui-rpc-api/src/rest/system.rs | 78 +- .../src/rest/transactions/execution.rs | 40 +- .../sui-rpc-api/src/rest/transactions/mod.rs | 47 +- .../src/rest/transactions/resolve/mod.rs | 28 +- crates/sui-rpc-api/src/types.rs | 16 +- crates/sui-rpc-api/tests/openapi.rs | 62 -- scripts/update_all_snapshots.sh | 1 - 22 files changed, 71 insertions(+), 2032 deletions(-) delete mode 100644 crates/sui-rpc-api/openapi/elements.html delete mode 100644 crates/sui-rpc-api/openapi/openapi.json delete mode 100644 crates/sui-rpc-api/openapi/swagger.html delete mode 100644 crates/sui-rpc-api/src/rest/openapi.rs delete mode 100644 crates/sui-rpc-api/tests/openapi.rs diff --git a/Cargo.lock b/Cargo.lock index a439fa6c60cd9..70e1e71311ba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2965,15 +2965,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "copy_dir" version = "0.1.3" @@ -3708,7 +3699,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", + "convert_case", "proc-macro2 1.0.87", "quote 1.0.37", "rustc_version", @@ -3994,30 +3985,6 @@ dependencies = [ "serde_with 2.1.0", ] -[[package]] -name = "documented" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feadfed35b96a5634e08fc503677ded669549ae2cf7f0b01d5964f09d95487fd" -dependencies = [ - "documented-macros", - "phf", - "thiserror 1.0.64", -] - -[[package]] -name = "documented-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973659d4a62084e32a7f9332509455436d33684b316bb3bc2bb6dcea51a68c63" -dependencies = [ - "convert_case 0.6.0", - "optfield", - "proc-macro2 1.0.87", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -9104,17 +9071,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "openapiv3" -version = "2.0.0" -source = "git+https://github.com/bmwill/openapiv3.git?rev=ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963#ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963" -dependencies = [ - "indexmap 2.2.6", - "schemars", - "serde", - "serde_json", -] - [[package]] name = "openssl-probe" version = "0.1.5" @@ -9204,17 +9160,6 @@ dependencies = [ "tokio-stream", ] -[[package]] -name = "optfield" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d" -dependencies = [ - "proc-macro2 1.0.87", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -15059,7 +15004,6 @@ dependencies = [ "bcs", "bytes", "diffy", - "documented", "fastcrypto", "http 1.1.0", "itertools 0.13.0", @@ -15067,7 +15011,6 @@ dependencies = [ "move-binary-format", "move-core-types", "mysten-network", - "openapiv3", "paste", "prometheus", "proptest", @@ -15077,11 +15020,9 @@ dependencies = [ "rand 0.8.5", "reqwest 0.12.5", "roaring", - "schemars", "serde", "serde_json", "serde_with 3.9.0", - "serde_yaml 0.8.26", "sui-protocol-config", "sui-sdk-types", "sui-transaction-builder 0.1.0", diff --git a/crates/sui-rpc-api/Cargo.toml b/crates/sui-rpc-api/Cargo.toml index 842e1bd9f9eff..60e141c9ce034 100644 --- a/crates/sui-rpc-api/Cargo.toml +++ b/crates/sui-rpc-api/Cargo.toml @@ -15,7 +15,6 @@ reqwest.workspace = true url.workspace = true serde.workspace = true serde_json.workspace = true -serde_yaml.workspace = true serde_with.workspace = true tap.workspace = true thiserror.workspace = true @@ -25,9 +24,6 @@ itertools.workspace = true sui-sdk-types.workspace = true sui-sdk-transaction-builder.workspace = true prometheus.workspace = true -openapiv3 = { git = "https://github.com/bmwill/openapiv3.git", rev = "ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963" } -schemars.workspace = true -documented = "0.6.0" http.workspace = true tower.workspace = true diff --git a/crates/sui-rpc-api/openapi/elements.html b/crates/sui-rpc-api/openapi/elements.html deleted file mode 100644 index 976a2c280615d..0000000000000 --- a/crates/sui-rpc-api/openapi/elements.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Sui Node Api - - - - - - - - - - diff --git a/crates/sui-rpc-api/openapi/openapi.json b/crates/sui-rpc-api/openapi/openapi.json deleted file mode 100644 index 08d902afc5c0b..0000000000000 --- a/crates/sui-rpc-api/openapi/openapi.json +++ /dev/null @@ -1,664 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Sui Node Api", - "description": "REST Api for interacting with the Sui Blockchain", - "contact": { - "name": "Mysten Labs", - "url": "https://github.com/MystenLabs/sui" - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "unknown" - }, - "servers": [ - { - "url": "/v2" - } - ], - "paths": { - "/": { - "get": { - "tags": [ - "General" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nGet basic information about the state of a Node", - "operationId": "Get NodeInfo", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "500": { - "description": "" - } - } - } - }, - "/-/health": { - "get": { - "tags": [ - "General" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nPerform a service health check\n\nBy default the health check only verifies that the latest checkpoint can be fetched from the\nnode's store before returning a 200. Optionally the `threshold_seconds` parameter can be\nprovided to test for how up to date the node needs to be to be considered healthy.", - "operationId": "Health Check", - "responses": { - "200": { - "description": "", - "content": { - "text/plain; charset=utf-8": {} - } - }, - "500": { - "description": "" - } - } - } - }, - "/checkpoints/{checkpoint}": { - "get": { - "tags": [ - "Checkpoint" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nFetch a Checkpoint\n\nFetch a checkpoint either by `CheckpointSequenceNumber` (checkpoint height) or by\n`CheckpointDigest` and optionally request its contents.\n\nIf the checkpoint has been pruned and is not available, a 410 will be returned.", - "operationId": "Get Checkpoint", - "parameters": [ - { - "in": "path", - "name": "checkpoint", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - }, - "410": { - "description": "" - }, - "500": { - "description": "" - } - } - } - }, - "/accounts/{account}/objects": { - "get": { - "tags": [ - "Account" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "ListAccountObjects", - "parameters": [ - { - "in": "path", - "name": "account", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-cursor": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - } - } - } - }, - "/objects/{object_id}": { - "get": { - "tags": [ - "Objects" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetObject", - "parameters": [ - { - "in": "path", - "name": "object_id", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/objects/{object_id}/version/{version}": { - "get": { - "tags": [ - "Objects" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetObjectWithVersion", - "parameters": [ - { - "in": "path", - "name": "object_id", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - }, - { - "in": "path", - "name": "version", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/objects/{object_id}/dynamic-fields": { - "get": { - "tags": [ - "Objects" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "ListDynamicFields", - "parameters": [ - { - "in": "path", - "name": "object_id", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-cursor": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - } - } - } - }, - "/checkpoints": { - "get": { - "tags": [ - "Checkpoint" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\nList Checkpoints\n\nRequest a page of checkpoints, and optionally their contents, ordered by\n`CheckpointSequenceNumber`.\n\nIf the requested page is below the Node's `lowest_available_checkpoint`, a 410 will be\nreturned.", - "operationId": "List Checkpoints", - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-cursor": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - }, - "410": { - "description": "" - }, - "500": { - "description": "" - } - } - } - }, - "/transactions/{transaction}": { - "get": { - "tags": [ - "Transactions" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetTransaction", - "parameters": [ - { - "in": "path", - "name": "transaction", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/transactions": { - "get": { - "tags": [ - "Transactions" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "ListTransactions", - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-cursor": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - }, - "410": { - "description": "" - } - } - }, - "post": { - "tags": [ - "Transactions" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "ExecuteTransaction", - "requestBody": { - "content": { - "application/bcs": {} - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/system/committee/{epoch}": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetCommittee", - "parameters": [ - { - "in": "path", - "name": "epoch", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/system/committee": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetLatestCommittee", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/system": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetSystemStateSummary", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/system/protocol": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetCurrentProtocolConfig", - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-min-supported-protocol-version": { - "style": "simple", - "schema": { - "type": "string" - } - }, - "x-sui-max-supported-protocol-version": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - } - } - } - }, - "/system/protocol/{version}": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetProtocolConfig", - "parameters": [ - { - "in": "path", - "name": "version", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "headers": { - "x-sui-min-supported-protocol-version": { - "style": "simple", - "schema": { - "type": "string" - } - }, - "x-sui-max-supported-protocol-version": { - "style": "simple", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/system/gas": { - "get": { - "tags": [ - "System" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetGasInfo", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/transactions/simulate": { - "post": { - "tags": [ - "Transactions" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "SimulateTransaction", - "requestBody": { - "content": { - "application/bcs": {} - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/transactions/resolve": { - "post": { - "tags": [ - "Transactions" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "ResolveTransaction", - "requestBody": { - "content": { - "application/json": {} - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/coins/{coin_type}": { - "get": { - "tags": [ - "Coins" - ], - "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", - "operationId": "GetCoinInfo", - "parameters": [ - { - "in": "path", - "name": "coin_type", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple" - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - }, - "404": { - "description": "" - } - } - } - }, - "/openapi": { - "get": { - "tags": [ - "OpenAPI" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nProvides a web UI for exploring the OpenAPI v3.1.0 definition for this service", - "operationId": "OpenAPI Explorer", - "responses": { - "200": { - "description": "", - "content": { - "text/html; charset=utf-8": {} - } - } - } - } - }, - "/openapi.json": { - "get": { - "tags": [ - "OpenAPI" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nReturn the OpenAPI v3.1.0 definition for this service as a JSON document", - "operationId": "openapi.json", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/openapi.yaml": { - "get": { - "tags": [ - "OpenAPI" - ], - "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nReturn the OpenAPI v3.1.0 definition for this service as a YAML document", - "operationId": "openapi.yaml", - "responses": { - "200": { - "description": "", - "content": { - "text/plain; charset=utf-8": {} - } - } - } - } - } - }, - "components": {}, - "tags": [ - { - "name": "Account" - }, - { - "name": "Checkpoint" - }, - { - "name": "Coins" - }, - { - "name": "General" - }, - { - "name": "Objects" - }, - { - "name": "OpenAPI" - }, - { - "name": "System" - }, - { - "name": "Transactions" - } - ] -} diff --git a/crates/sui-rpc-api/openapi/swagger.html b/crates/sui-rpc-api/openapi/swagger.html deleted file mode 100644 index 7172ca0e3f0bc..0000000000000 --- a/crates/sui-rpc-api/openapi/swagger.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - SwaggerUI - - - -
- - - - diff --git a/crates/sui-rpc-api/src/lib.rs b/crates/sui-rpc-api/src/lib.rs index 9a7b25670784c..6935f7797c1b0 100644 --- a/crates/sui-rpc-api/src/lib.rs +++ b/crates/sui-rpc-api/src/lib.rs @@ -148,7 +148,7 @@ impl RpcService { } } -#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "lowercase")] pub enum Direction { Ascending, diff --git a/crates/sui-rpc-api/src/rest/accounts.rs b/crates/sui-rpc-api/src/rest/accounts.rs index c4c95a9c90dab..3fe7758372ea2 100644 --- a/crates/sui-rpc-api/src/rest/accounts.rs +++ b/crates/sui-rpc-api/src/rest/accounts.rs @@ -1,14 +1,13 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use super::{ApiEndpoint, RouteHandler}; use crate::reader::StateReader; -use crate::rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}; use crate::Result; use crate::{rest::PageCursor, RpcService, RpcServiceError}; use axum::extract::Query; use axum::extract::{Path, State}; use axum::Json; -use openapiv3::v3_1::Operation; use sui_sdk_types::{Address, ObjectId, StructTag, Version}; use sui_types::sui_sdk_types_conversions::struct_tag_core_to_sdk; use tap::Pipe; @@ -24,23 +23,7 @@ impl ApiEndpoint for ListAccountObjects { "/accounts/{account}/objects" } - fn operation(&self, generator: &mut schemars::gen::SchemaGenerator) -> Operation { - OperationBuilder::new() - .tag("Account") - .operation_id("ListAccountObjects") - .path_parameter::
("account", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::>(generator) - .header::(crate::types::X_SUI_CURSOR, generator) - .build(), - ) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), list_account_objects) } } diff --git a/crates/sui-rpc-api/src/rest/checkpoints.rs b/crates/sui-rpc-api/src/rest/checkpoints.rs index 5a5c7fa2cccef..c2eb6e0037988 100644 --- a/crates/sui-rpc-api/src/rest/checkpoints.rs +++ b/crates/sui-rpc-api/src/rest/checkpoints.rs @@ -7,14 +7,13 @@ use axum::Json; use sui_sdk_types::{CheckpointSequenceNumber, SignedCheckpointSummary}; use sui_types::storage::ReadStore; +use super::{ApiEndpoint, RouteHandler}; use crate::reader::StateReader; -use crate::rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}; use crate::rest::PageCursor; use crate::service::checkpoints::CheckpointId; use crate::types::{CheckpointResponse, GetCheckpointOptions}; use crate::Result; use crate::{Direction, RpcService}; -use documented::Documented; /// Fetch a Checkpoint /// @@ -22,7 +21,6 @@ use documented::Documented; /// `CheckpointDigest` and optionally request its contents. /// /// If the checkpoint has been pruned and is not available, a 410 will be returned. -#[derive(Documented)] pub struct GetCheckpoint; impl ApiEndpoint for GetCheckpoint { @@ -34,32 +32,6 @@ impl ApiEndpoint for GetCheckpoint { "/checkpoints/{checkpoint}" } - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Checkpoint") - .operation_id("Get Checkpoint") - .description(Self::DOCS) - .path_parameter::("checkpoint", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_checkpoint) } @@ -74,7 +46,7 @@ async fn get_checkpoint( } /// Query parameters for the GetCheckpoint endpoint -#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct GetCheckpointQueryParameters { /// Request `CheckpointContents` be included in the response #[serde(default)] @@ -88,7 +60,6 @@ pub struct GetCheckpointQueryParameters { /// /// If the requested page is below the Node's `lowest_available_checkpoint`, a 410 will be /// returned. -#[derive(Documented)] pub struct ListCheckpoints; impl ApiEndpoint for ListCheckpoints { @@ -100,34 +71,6 @@ impl ApiEndpoint for ListCheckpoints { "/checkpoints" } - fn stable(&self) -> bool { - // Before making this api stable we'll need to properly handle the options that can be - // provided as inputs. - false - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Checkpoint") - .operation_id("List Checkpoints") - .description(Self::DOCS) - .query_parameters::(generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::>(generator) - .header::(crate::types::X_SUI_CURSOR, generator) - .build(), - ) - .response(410, ResponseBuilder::new().build()) - .response(500, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), list_checkpoints) } @@ -199,7 +142,7 @@ async fn list_checkpoints( Ok((PageCursor(cursor), Json(checkpoints))) } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ListCheckpointsPaginationParameters { /// Page size limit for the response. /// diff --git a/crates/sui-rpc-api/src/rest/coins.rs b/crates/sui-rpc-api/src/rest/coins.rs index 816c1397cbe3f..cef5719cbfe2f 100644 --- a/crates/sui-rpc-api/src/rest/coins.rs +++ b/crates/sui-rpc-api/src/rest/coins.rs @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}; +use super::{ApiEndpoint, RouteHandler}; use crate::RpcService; use crate::RpcServiceError; use crate::{reader::StateReader, Result}; @@ -22,25 +22,7 @@ impl ApiEndpoint for GetCoinInfo { "/coins/{coin_type}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Coins") - .operation_id("GetCoinInfo") - .path_parameter::("coin_type", generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_coin_info) } } diff --git a/crates/sui-rpc-api/src/rest/committee.rs b/crates/sui-rpc-api/src/rest/committee.rs index 1b648abb87448..d31ff3b3b339b 100644 --- a/crates/sui-rpc-api/src/rest/committee.rs +++ b/crates/sui-rpc-api/src/rest/committee.rs @@ -1,10 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ - rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}, - Result, RpcService, -}; +use super::{ApiEndpoint, RouteHandler}; +use crate::{Result, RpcService}; use axum::{ extract::{Path, State}, Json, @@ -22,22 +20,6 @@ impl ApiEndpoint for GetLatestCommittee { "/system/committee" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetLatestCommittee") - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_latest_committee) } @@ -58,24 +40,6 @@ impl ApiEndpoint for GetCommittee { "/system/committee/{epoch}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetCommittee") - .path_parameter::("epoch", generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_committee) } diff --git a/crates/sui-rpc-api/src/rest/health.rs b/crates/sui-rpc-api/src/rest/health.rs index 39864de11f955..e4930227979bc 100644 --- a/crates/sui-rpc-api/src/rest/health.rs +++ b/crates/sui-rpc-api/src/rest/health.rs @@ -1,19 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ - rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}, - RpcService, -}; +use super::{ApiEndpoint, RouteHandler}; +use crate::RpcService; use axum::extract::{Query, State}; -use documented::Documented; /// Perform a service health check /// /// By default the health check only verifies that the latest checkpoint can be fetched from the /// node's store before returning a 200. Optionally the `threshold_seconds` parameter can be /// provided to test for how up to date the node needs to be to be considered healthy. -#[derive(Documented)] pub struct HealthCheck; impl ApiEndpoint for HealthCheck { @@ -25,30 +21,12 @@ impl ApiEndpoint for HealthCheck { "/-/health" } - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("General") - .operation_id("Health Check") - .description(Self::DOCS) - .query_parameters::(generator) - .response(200, ResponseBuilder::new().text_content().build()) - .response(500, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), health) } } -#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Threshold { /// The threshold, or delta, between the server's system time and the timestamp in the most /// recently executed checkpoint for which the server is considered to be healthy. diff --git a/crates/sui-rpc-api/src/rest/info.rs b/crates/sui-rpc-api/src/rest/info.rs index 435f91bb80d42..4ce320989cfe4 100644 --- a/crates/sui-rpc-api/src/rest/info.rs +++ b/crates/sui-rpc-api/src/rest/info.rs @@ -1,15 +1,13 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}; +use super::{ApiEndpoint, RouteHandler}; use crate::types::NodeInfo; use crate::{Result, RpcService}; use axum::extract::State; use axum::Json; -use documented::Documented; /// Get basic information about the state of a Node -#[derive(Documented)] pub struct GetNodeInfo; impl ApiEndpoint for GetNodeInfo { @@ -21,29 +19,7 @@ impl ApiEndpoint for GetNodeInfo { "/" } - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("General") - .operation_id("Get NodeInfo") - .description(Self::DOCS) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(500, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_node_info) } } diff --git a/crates/sui-rpc-api/src/rest/mod.rs b/crates/sui-rpc-api/src/rest/mod.rs index 03a80ebe7dd8b..60c191d267fb0 100644 --- a/crates/sui-rpc-api/src/rest/mod.rs +++ b/crates/sui-rpc-api/src/rest/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; +use axum::{handler::Handler, http::Method, routing::MethodRouter}; use axum::{ response::{Redirect, ResponseParts}, routing::get, @@ -10,7 +11,6 @@ use axum::{ }; use crate::{reader::StateReader, RpcService}; -use openapi::ApiEndpoint; pub mod accept; pub mod accounts; @@ -21,7 +21,6 @@ pub mod content_type; pub mod health; pub mod info; pub mod objects; -pub mod openapi; pub mod system; pub mod transactions; @@ -30,11 +29,9 @@ pub const APPLICATION_BCS: &str = "application/bcs"; pub const APPLICATION_JSON: &str = "application/json"; pub const ENDPOINTS: &[&dyn ApiEndpoint] = &[ - // stable APIs &info::GetNodeInfo, &health::HealthCheck, &checkpoints::GetCheckpoint, - // unstable APIs &accounts::ListAccountObjects, &objects::GetObject, &objects::GetObjectWithVersion, @@ -55,12 +52,21 @@ pub const ENDPOINTS: &[&dyn ApiEndpoint] = &[ ]; pub fn build_rest_router(service: RpcService) -> axum::Router { - let mut api = openapi::Api::new(info(service.software_version())); + let mut api = Router::new(); - api.register_endpoints(ENDPOINTS.iter().copied()); + for endpoint in ENDPOINTS { + let handler = endpoint.handler(); + assert_eq!(handler.method(), endpoint.method()); + + // we need to replace any path parameters wrapped in braces to be prefaced by a colon + // until axum updates matchit: https://github.com/tokio-rs/axum/pull/2645 + let path = endpoint.path().replace('{', ":").replace('}', ""); + + api = api.route(&path, handler.handler); + } Router::new() - .nest("/v2/", api.to_router().with_state(service)) + .nest("/v2/", api.with_state(service)) .route("/v2", get(|| async { Redirect::permanent("/v2/") })) // Previously the service used to be hosted at `/rest`. In an effort to migrate folks // to the new versioned route, we'll issue redirects from `/rest` -> `/v2`. @@ -110,60 +116,34 @@ impl axum::extract::FromRef } } -pub fn info(version: &'static str) -> openapiv3::v3_1::Info { - use openapiv3::v3_1::Contact; - use openapiv3::v3_1::License; - - openapiv3::v3_1::Info { - title: "Sui Node Api".to_owned(), - description: Some("REST Api for interacting with the Sui Blockchain".to_owned()), - contact: Some(Contact { - name: Some("Mysten Labs".to_owned()), - url: Some("https://github.com/MystenLabs/sui".to_owned()), - ..Default::default() - }), - license: Some(License { - name: "Apache 2.0".to_owned(), - url: Some("https://www.apache.org/licenses/LICENSE-2.0.html".to_owned()), - ..Default::default() - }), - version: version.to_owned(), - ..Default::default() - } -} - async fn redirect(axum::extract::Path(path): axum::extract::Path) -> Redirect { Redirect::permanent(&format!("/v2/{path}")) } -pub(crate) mod _schemars { - use schemars::schema::InstanceType; - use schemars::schema::Metadata; - use schemars::schema::SchemaObject; - use schemars::JsonSchema; - - pub(crate) struct U64; - - impl JsonSchema for U64 { - fn schema_name() -> String { - "u64".to_owned() - } - - fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - SchemaObject { - metadata: Some(Box::new(Metadata { - description: Some("Radix-10 encoded 64-bit unsigned integer".to_owned()), - ..Default::default() - })), - instance_type: Some(InstanceType::String.into()), - format: Some("u64".to_owned()), - ..Default::default() - } - .into() - } - - fn is_referenceable() -> bool { - false - } +pub trait ApiEndpoint { + fn method(&self) -> Method; + fn path(&self) -> &'static str; + fn handler(&self) -> RouteHandler; +} + +pub struct RouteHandler { + method: axum::http::Method, + handler: MethodRouter, +} + +impl RouteHandler { + pub fn new(method: axum::http::Method, handler: H) -> Self + where + H: Handler, + T: 'static, + S: Send + Sync + 'static, + { + let handler = MethodRouter::new().on(method.clone().try_into().unwrap(), handler); + + Self { method, handler } + } + + pub fn method(&self) -> &axum::http::Method { + &self.method } } diff --git a/crates/sui-rpc-api/src/rest/objects.rs b/crates/sui-rpc-api/src/rest/objects.rs index 18cdac6427dc0..151ad0c65b6fb 100644 --- a/crates/sui-rpc-api/src/rest/objects.rs +++ b/crates/sui-rpc-api/src/rest/objects.rs @@ -1,13 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use super::{ApiEndpoint, RouteHandler}; use crate::types::{GetObjectOptions, ObjectResponse}; -use crate::{ - reader::StateReader, - rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}, - rest::PageCursor, - Result, RpcService, RpcServiceError, -}; +use crate::{reader::StateReader, rest::PageCursor, Result, RpcService, RpcServiceError}; use axum::extract::Query; use axum::extract::{Path, State}; use axum::Json; @@ -31,26 +27,7 @@ impl ApiEndpoint for GetObject { "/objects/{object_id}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Objects") - .operation_id("GetObject") - .path_parameter::("object_id", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_object) } } @@ -76,27 +53,7 @@ impl ApiEndpoint for GetObjectWithVersion { "/objects/{object_id}/version/{version}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Objects") - .operation_id("GetObjectWithVersion") - .path_parameter::("object_id", generator) - .path_parameter::("version", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_object_with_version) } } @@ -122,26 +79,7 @@ impl ApiEndpoint for ListDynamicFields { "/objects/{object_id}/dynamic-fields" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Objects") - .operation_id("ListDynamicFields") - .path_parameter::("object_id", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::>(generator) - .header::(crate::types::X_SUI_CURSOR, generator) - .build(), - ) - .build() - } - - fn handler(&self) -> crate::rest::openapi::RouteHandler { + fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), list_dynamic_fields) } } diff --git a/crates/sui-rpc-api/src/rest/openapi.rs b/crates/sui-rpc-api/src/rest/openapi.rs deleted file mode 100644 index c6e109c6abb18..0000000000000 --- a/crates/sui-rpc-api/src/rest/openapi.rs +++ /dev/null @@ -1,685 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::HashSet, - sync::{Arc, OnceLock}, -}; - -use axum::{ - body::Bytes, - extract::State, - handler::Handler, - http::Method, - response::Html, - routing::{get, MethodRouter}, - Router, -}; -use documented::Documented; -use openapiv3::v3_1::{ - Components, Header, Info, MediaType, OpenApi, Operation, Parameter, ParameterData, PathItem, - Paths, ReferenceOr, RequestBody, Response, SchemaObject, Tag, -}; -use schemars::{gen::SchemaGenerator, JsonSchema}; -use tap::Pipe; - -const STABLE_BADGE_MARKDOWN: &str = - "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\n"; - -const UNSTABLE_BADGE_MARKDOWN: &str = - "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n"; - -pub trait ApiEndpoint { - fn method(&self) -> Method; - fn path(&self) -> &'static str; - fn hidden(&self) -> bool { - false - } - - /// Indicates the stability of the API - /// - /// Stable APIs are enabled in the REST service by default, unstable ones need to be explicitly - /// configured to be enabled via config. - /// - /// Both stable and unstable APIs have a badge, indicating the api's stability, added to the - /// top of the description field of their OpenAPI definition. - /// - /// By default all apis are unstable, individual apis need to explicitly opt-in to being stable - fn stable(&self) -> bool { - false - } - - fn operation(&self, _generator: &mut SchemaGenerator) -> Operation { - Operation::default() - } - - fn handler(&self) -> RouteHandler; -} - -pub struct RouteHandler { - method: axum::http::Method, - handler: MethodRouter, -} - -impl RouteHandler { - pub fn new(method: axum::http::Method, handler: H) -> Self - where - H: Handler, - T: 'static, - S: Send + Sync + 'static, - { - let handler = MethodRouter::new().on(method.clone().try_into().unwrap(), handler); - - Self { method, handler } - } - - pub fn method(&self) -> &axum::http::Method { - &self.method - } -} - -pub struct Api<'a, S> { - endpoints: Vec<&'a dyn ApiEndpoint>, - info: Info, -} - -impl<'a, S> Api<'a, S> { - pub fn new(info: Info) -> Self { - Self { - endpoints: Vec::new(), - info, - } - } - - pub fn register_endpoints>>( - &mut self, - endpoints: I, - ) { - self.endpoints.extend(endpoints); - } - - pub fn to_router(&self) -> axum::Router - where - S: Clone + Send + Sync + 'static, - { - let mut router = OpenApiDocument::new(self.openapi()).into_router(); - for endpoint in &self.endpoints { - let handler = endpoint.handler(); - assert_eq!(handler.method(), endpoint.method()); - - // we need to replace any path parameters wrapped in braces to be prefaced by a colon - // until axum updates matchit: https://github.com/tokio-rs/axum/pull/2645 - let path = endpoint.path().replace('{', ":").replace('}', ""); - - router = router.route(&path, handler.handler); - } - - router - } - - pub fn openapi(&self) -> openapiv3::versioned::OpenApi { - self.gen_openapi(self.info.clone()) - } - - /// Internal routine for constructing the OpenAPI definition describing this - /// API in its JSON form. - fn gen_openapi(&self, info: Info) -> openapiv3::versioned::OpenApi { - let mut openapi = OpenApi { - info, - ..Default::default() - }; - - let settings = schemars::gen::SchemaSettings::draft07().with(|s| { - s.definitions_path = "#/components/schemas/".into(); - s.option_add_null_type = false; - }); - let mut generator = schemars::gen::SchemaGenerator::new(settings); - let mut tags = HashSet::new(); - - let paths = openapi - .paths - .get_or_insert(openapiv3::v3_1::Paths::default()); - - for endpoint in &self.endpoints { - // Skip hidden endpoints - if endpoint.hidden() { - continue; - } - - Self::register_endpoint(*endpoint, &mut generator, paths, &mut tags); - } - - // Add OpenApi routes themselves - let openapi_endpoints: [&dyn ApiEndpoint<_>; 3] = - [&OpenApiExplorer, &OpenApiJson, &OpenApiYaml]; - for endpoint in openapi_endpoints { - Self::register_endpoint(endpoint, &mut generator, paths, &mut tags); - } - - let components = &mut openapi.components.get_or_insert_with(Components::default); - - // Add the schemas for which we generated references. - let schemas = &mut components.schemas; - - generator - .into_root_schema_for::<()>() - .definitions - .into_iter() - .for_each(|(key, schema)| { - let schema_object = SchemaObject { - json_schema: schema, - external_docs: None, - example: None, - }; - schemas.insert(key, schema_object); - }); - - openapi.tags = tags - .into_iter() - .map(|tag| Tag { - name: tag, - ..Default::default() - }) - .collect(); - // Sort the tags for stability - openapi.tags.sort_by(|a, b| a.name.cmp(&b.name)); - - openapi.servers = vec![openapiv3::v3_1::Server { - url: "/v2".into(), - ..Default::default() - }]; - - openapiv3::versioned::OpenApi::Version31(openapi) - } - - fn register_endpoint( - endpoint: &dyn ApiEndpoint, - generator: &mut schemars::gen::SchemaGenerator, - paths: &mut Paths, - tags: &mut HashSet, - ) { - let path = paths - .paths - .entry(endpoint.path().to_owned()) - .or_insert(ReferenceOr::Item(PathItem::default())); - - let pathitem = match path { - openapiv3::v3_1::ReferenceOr::Item(ref mut item) => item, - _ => panic!("reference not expected"), - }; - - let method_ref = match endpoint.method() { - Method::DELETE => &mut pathitem.delete, - Method::GET => &mut pathitem.get, - Method::HEAD => &mut pathitem.head, - Method::OPTIONS => &mut pathitem.options, - Method::PATCH => &mut pathitem.patch, - Method::POST => &mut pathitem.post, - Method::PUT => &mut pathitem.put, - Method::TRACE => &mut pathitem.trace, - other => panic!("unexpected method `{}`", other), - }; - - let mut operation = endpoint.operation(generator); - - if endpoint.stable() { - operation - .description - .get_or_insert_with(String::new) - .insert_str(0, STABLE_BADGE_MARKDOWN); - } else { - operation - .description - .get_or_insert_with(String::new) - .insert_str(0, UNSTABLE_BADGE_MARKDOWN); - } - - // Collect tags defined by this operation - tags.extend(operation.tags.clone()); - - method_ref.replace(operation); - } -} - -pub struct OpenApiDocument { - openapi: openapiv3::versioned::OpenApi, - json: OnceLock, - yaml: OnceLock, - ui: &'static str, -} - -impl OpenApiDocument { - pub fn new(openapi: openapiv3::versioned::OpenApi) -> Self { - const OPENAPI_UI: &str = include_str!("../../openapi/elements.html"); - // const OPENAPI_UI: &str = include_str!("../../openapi/swagger.html"); - - Self { - openapi, - json: OnceLock::new(), - yaml: OnceLock::new(), - ui: OPENAPI_UI, - } - } - - fn openapi(&self) -> &openapiv3::versioned::OpenApi { - &self.openapi - } - - fn json(&self) -> Bytes { - self.json - .get_or_init(|| { - self.openapi() - .pipe(serde_json::to_string_pretty) - .unwrap() - .pipe(Bytes::from) - }) - .clone() - } - - fn yaml(&self) -> Bytes { - self.yaml - .get_or_init(|| { - self.openapi() - .pipe(serde_yaml::to_string) - .unwrap() - .pipe(Bytes::from) - }) - .clone() - } - - fn ui(&self) -> &'static str { - self.ui - } - - pub fn into_router(self) -> Router { - Router::new() - .route("/openapi", get(openapi_ui)) - .route("/openapi.json", get(openapi_json)) - .route("/openapi.yaml", get(openapi_yaml)) - .with_state(Arc::new(self)) - } -} - -/// Return the OpenAPI v3.1.0 definition for this service as a JSON document -#[derive(Documented)] -pub struct OpenApiJson; - -impl ApiEndpoint> for OpenApiJson { - fn method(&self) -> axum::http::Method { - axum::http::Method::GET - } - - fn path(&self) -> &'static str { - "/openapi.json" - } - - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - _generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("OpenAPI") - .operation_id("openapi.json") - .description(Self::DOCS) - .response( - 200, - ResponseBuilder::new() - .content(mime::APPLICATION_JSON.as_ref(), MediaType::default()) - .build(), - ) - .build() - } - - fn handler(&self) -> RouteHandler> { - RouteHandler::new(self.method(), openapi_json) - } -} - -/// Return the OpenAPI v3.1.0 definition for this service as a YAML document -#[derive(Documented)] -pub struct OpenApiYaml; - -impl ApiEndpoint> for OpenApiYaml { - fn method(&self) -> axum::http::Method { - axum::http::Method::GET - } - - fn path(&self) -> &'static str { - "/openapi.yaml" - } - - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - _generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("OpenAPI") - .operation_id("openapi.yaml") - .description(Self::DOCS) - .response( - 200, - ResponseBuilder::new() - .content(mime::TEXT_PLAIN_UTF_8.as_ref(), MediaType::default()) - .build(), - ) - .build() - } - - fn handler(&self) -> RouteHandler> { - RouteHandler::new(self.method(), openapi_yaml) - } -} - -/// Provides a web UI for exploring the OpenAPI v3.1.0 definition for this service -#[derive(Documented)] -pub struct OpenApiExplorer; - -impl ApiEndpoint> for OpenApiExplorer { - fn method(&self) -> axum::http::Method { - axum::http::Method::GET - } - - fn path(&self) -> &'static str { - "/openapi" - } - - fn stable(&self) -> bool { - true - } - - fn operation( - &self, - _generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("OpenAPI") - .operation_id("OpenAPI Explorer") - .description(Self::DOCS) - .response( - 200, - ResponseBuilder::new() - .content(mime::TEXT_HTML_UTF_8.as_ref(), MediaType::default()) - .build(), - ) - .build() - } - - fn handler(&self) -> RouteHandler> { - RouteHandler::new(self.method(), openapi_ui) - } -} - -async fn openapi_json( - State(document): State>, -) -> impl axum::response::IntoResponse { - ( - [( - axum::http::header::CONTENT_TYPE, - axum::http::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), - )], - document.json(), - ) -} - -async fn openapi_yaml( - State(document): State>, -) -> impl axum::response::IntoResponse { - ( - [( - axum::http::header::CONTENT_TYPE, - axum::http::HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), - )], - document.yaml(), - ) -} - -async fn openapi_ui(State(document): State>) -> Html<&'static str> { - Html(document.ui()) -} - -fn path_parameter( - name: &str, - generator: &mut SchemaGenerator, -) -> ReferenceOr { - let schema_object = SchemaObject { - json_schema: generator.subschema_for::(), - external_docs: None, - example: None, - }; - - let parameter_data = ParameterData { - name: name.into(), - required: true, - format: openapiv3::v3_1::ParameterSchemaOrContent::Schema(schema_object), - description: None, - deprecated: None, - example: None, - examples: Default::default(), - explode: None, - extensions: Default::default(), - }; - - ReferenceOr::Item(Parameter::Path { - parameter_data, - style: openapiv3::v3_1::PathStyle::Simple, - }) -} - -#[allow(unused)] -fn query_parameters(generator: &mut SchemaGenerator) -> Vec> { - let mut params = Vec::new(); - - let schema = generator.root_schema_for::().schema; - - let Some(object) = &schema.object else { - return params; - }; - - for (name, schema) in &object.properties { - let s = schema.clone().into_object(); - - params.push(ReferenceOr::Item(Parameter::Query { - parameter_data: ParameterData { - name: name.clone(), - description: s.metadata.as_ref().and_then(|m| m.description.clone()), - required: object.required.contains(name), - format: openapiv3::v3_1::ParameterSchemaOrContent::Schema(SchemaObject { - json_schema: s.into(), - example: None, - external_docs: None, - }), - extensions: Default::default(), - deprecated: None, - example: None, - examples: Default::default(), - explode: None, - }, - allow_reserved: false, - style: openapiv3::v3_1::QueryStyle::Form, - allow_empty_value: None, - })); - } - - params -} - -#[derive(Default)] -pub struct OperationBuilder { - inner: Operation, -} - -impl OperationBuilder { - pub fn new() -> Self { - Self { - inner: Default::default(), - } - } - - pub fn build(&mut self) -> Operation { - self.inner.clone() - } - - pub fn tag>(&mut self, tag: T) -> &mut Self { - self.inner.tags.push(tag.into()); - self - } - - pub fn summary>(&mut self, summary: T) -> &mut Self { - self.inner.summary = Some(summary.into()); - self - } - - pub fn description>(&mut self, description: T) -> &mut Self { - self.inner.description = Some(description.into()); - self - } - - pub fn operation_id>(&mut self, operation_id: T) -> &mut Self { - self.inner.operation_id = Some(operation_id.into()); - self - } - - // pub fn path_parameter( - pub fn path_parameter(&mut self, name: &str, generator: &mut SchemaGenerator) -> &mut Self { - self.inner - .parameters - .push(path_parameter::(name, generator)); - self - } - - // pub fn query_parameters( - pub fn query_parameters(&mut self, _generator: &mut SchemaGenerator) -> &mut Self { - // self.inner - // .parameters - // .extend(query_parameters::(generator)); - self - } - - pub fn response(&mut self, status_code: u16, response: Response) -> &mut Self { - let responses = self.inner.responses.get_or_insert(Default::default()); - responses.responses.insert( - openapiv3::v3_1::StatusCode::Code(status_code), - ReferenceOr::Item(response), - ); - - self - } - - pub fn request_body(&mut self, request_body: RequestBody) -> &mut Self { - self.inner.request_body = Some(ReferenceOr::Item(request_body)); - self - } -} - -#[derive(Default)] -pub struct ResponseBuilder { - inner: Response, -} - -impl ResponseBuilder { - pub fn new() -> Self { - Self { - inner: Default::default(), - } - } - - pub fn build(&mut self) -> Response { - self.inner.clone() - } - - pub fn header( - &mut self, - name: &str, - generator: &mut SchemaGenerator, - ) -> &mut Self { - let schema_object = SchemaObject { - json_schema: generator.subschema_for::(), - external_docs: None, - example: None, - }; - - let header = ReferenceOr::Item(Header { - description: None, - style: Default::default(), - required: false, - deprecated: None, - format: openapiv3::v3_1::ParameterSchemaOrContent::Schema(schema_object), - example: None, - examples: Default::default(), - extensions: Default::default(), - }); - - self.inner.headers.insert(name.into(), header); - self - } - - pub fn content>( - &mut self, - content_type: T, - media_type: MediaType, - ) -> &mut Self { - self.inner.content.insert(content_type.into(), media_type); - self - } - - pub fn json_content(&mut self, _generator: &mut SchemaGenerator) -> &mut Self { - self.content(mime::APPLICATION_JSON.as_ref(), MediaType::default()) - } - - pub fn text_content(&mut self) -> &mut Self { - self.content(mime::TEXT_PLAIN_UTF_8.as_ref(), MediaType::default()) - } -} - -#[derive(Default)] -pub struct RequestBodyBuilder { - inner: RequestBody, -} - -impl RequestBodyBuilder { - pub fn new() -> Self { - Self { - inner: Default::default(), - } - } - - pub fn build(&mut self) -> RequestBody { - self.inner.clone() - } - - pub fn content>( - &mut self, - content_type: T, - media_type: MediaType, - ) -> &mut Self { - self.inner.content.insert(content_type.into(), media_type); - self - } - - // pub fn json_content(&mut self, generator: &mut SchemaGenerator) -> &mut Self { - pub fn json_content(&mut self, _generator: &mut SchemaGenerator) -> &mut Self { - // let schema_object = SchemaObject { - // json_schema: generator.subschema_for::(), - // external_docs: None, - // example: None, - // }; - // let media_type = MediaType { - // schema: Some(schema_object), - // ..Default::default() - // }; - - self.content(mime::APPLICATION_JSON.as_ref(), MediaType::default()) - } - - pub fn bcs_content(&mut self) -> &mut Self { - self.content(crate::rest::APPLICATION_BCS, MediaType::default()) - } -} diff --git a/crates/sui-rpc-api/src/rest/system.rs b/crates/sui-rpc-api/src/rest/system.rs index 7dea7a4255321..9cefc1d242ca7 100644 --- a/crates/sui-rpc-api/src/rest/system.rs +++ b/crates/sui-rpc-api/src/rest/system.rs @@ -1,12 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{ - reader::StateReader, - rest::accept::AcceptFormat, - rest::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler}, - Result, RpcService, RpcServiceError, -}; +use super::{ApiEndpoint, RouteHandler}; +use crate::{reader::StateReader, rest::accept::AcceptFormat, Result, RpcService, RpcServiceError}; use axum::{ extract::{Path, State}, Json, @@ -27,22 +23,6 @@ impl ApiEndpoint for GetSystemStateSummary { "/system" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetSystemStateSummary") - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_system_state_summary) } @@ -487,24 +467,6 @@ impl ApiEndpoint for GetCurrentProtocolConfig { "/system/protocol" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetCurrentProtocolConfig") - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .header::(X_SUI_MIN_SUPPORTED_PROTOCOL_VERSION, generator) - .header::(X_SUI_MAX_SUPPORTED_PROTOCOL_VERSION, generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_current_protocol_config) } @@ -546,26 +508,6 @@ impl ApiEndpoint for GetProtocolConfig { "/system/protocol/{version}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetProtocolConfig") - .path_parameter::("version", generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .header::(X_SUI_MIN_SUPPORTED_PROTOCOL_VERSION, generator) - .header::(X_SUI_MAX_SUPPORTED_PROTOCOL_VERSION, generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_protocol_config) } @@ -685,22 +627,6 @@ impl ApiEndpoint for GetGasInfo { "/system/gas" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("System") - .operation_id("GetGasInfo") - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_gas_info) } diff --git a/crates/sui-rpc-api/src/rest/transactions/execution.rs b/crates/sui-rpc-api/src/rest/transactions/execution.rs index ab04c32822dbb..70d44e483b4db 100644 --- a/crates/sui-rpc-api/src/rest/transactions/execution.rs +++ b/crates/sui-rpc-api/src/rest/transactions/execution.rs @@ -1,10 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use super::{ApiEndpoint, RouteHandler}; use crate::response::Bcs; -use crate::rest::openapi::{ - ApiEndpoint, OperationBuilder, RequestBodyBuilder, ResponseBuilder, RouteHandler, -}; use crate::types::ExecuteTransactionOptions; use crate::types::ExecuteTransactionResponse; use crate::{Result, RpcService}; @@ -26,24 +24,6 @@ impl ApiEndpoint for ExecuteTransaction { "/transactions" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Transactions") - .operation_id("ExecuteTransaction") - .query_parameters::(generator) - .request_body(RequestBodyBuilder::new().bcs_content().build()) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), execute_transaction) } @@ -77,24 +57,6 @@ impl ApiEndpoint for SimulateTransaction { "/transactions/simulate" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Transactions") - .operation_id("SimulateTransaction") - .query_parameters::(generator) - .request_body(RequestBodyBuilder::new().bcs_content().build()) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), simulate_transaction) } diff --git a/crates/sui-rpc-api/src/rest/transactions/mod.rs b/crates/sui-rpc-api/src/rest/transactions/mod.rs index 6cda5ba8b98ac..2f5cedd8ab1e1 100644 --- a/crates/sui-rpc-api/src/rest/transactions/mod.rs +++ b/crates/sui-rpc-api/src/rest/transactions/mod.rs @@ -19,10 +19,7 @@ use sui_sdk_types::CheckpointSequenceNumber; use sui_sdk_types::TransactionDigest; use tap::Pipe; -use crate::rest::openapi::ApiEndpoint; -use crate::rest::openapi::OperationBuilder; -use crate::rest::openapi::ResponseBuilder; -use crate::rest::openapi::RouteHandler; +use super::{ApiEndpoint, RouteHandler}; use crate::rest::PageCursor; use crate::types::GetTransactionOptions; use crate::types::TransactionResponse; @@ -42,25 +39,6 @@ impl ApiEndpoint for GetTransaction { "/transactions/{transaction}" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Transactions") - .operation_id("GetTransaction") - .path_parameter::("transaction", generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .response(404, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), get_transaction) } @@ -104,26 +82,6 @@ impl ApiEndpoint for ListTransactions { "/transactions" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Transactions") - .operation_id("ListTransactions") - .query_parameters::(generator) - .query_parameters::(generator) - .response( - 200, - ResponseBuilder::new() - .json_content::>(generator) - .header::(crate::types::X_SUI_CURSOR, generator) - .build(), - ) - .response(410, ResponseBuilder::new().build()) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), list_transactions) } @@ -250,10 +208,9 @@ impl serde::Serialize for TransactionCursor { } } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ListTransactionsCursorParameters { pub limit: Option, - #[schemars(with = "Option")] pub start: Option, pub direction: Option, } diff --git a/crates/sui-rpc-api/src/rest/transactions/resolve/mod.rs b/crates/sui-rpc-api/src/rest/transactions/resolve/mod.rs index 140e72f593354..db2701b79aeed 100644 --- a/crates/sui-rpc-api/src/rest/transactions/resolve/mod.rs +++ b/crates/sui-rpc-api/src/rest/transactions/resolve/mod.rs @@ -6,12 +6,8 @@ use std::collections::HashMap; use super::execution::SimulateTransactionQueryParameters; use super::TransactionSimulationResponse; +use super::{ApiEndpoint, RouteHandler}; use crate::reader::StateReader; -use crate::rest::openapi::ApiEndpoint; -use crate::rest::openapi::OperationBuilder; -use crate::rest::openapi::RequestBodyBuilder; -use crate::rest::openapi::ResponseBuilder; -use crate::rest::openapi::RouteHandler; use crate::service::objects::ObjectNotFoundError; use crate::Result; use crate::RpcService; @@ -55,28 +51,6 @@ impl ApiEndpoint for ResolveTransaction { "/transactions/resolve" } - fn operation( - &self, - generator: &mut schemars::gen::SchemaGenerator, - ) -> openapiv3::v3_1::Operation { - OperationBuilder::new() - .tag("Transactions") - .operation_id("ResolveTransaction") - .query_parameters::(generator) - .request_body( - RequestBodyBuilder::new() - .json_content::(generator) - .build(), - ) - .response( - 200, - ResponseBuilder::new() - .json_content::(generator) - .build(), - ) - .build() - } - fn handler(&self) -> RouteHandler { RouteHandler::new(self.method(), resolve_transaction) } diff --git a/crates/sui-rpc-api/src/types.rs b/crates/sui-rpc-api/src/types.rs index 8c9230295dee0..087bd1934fd11 100644 --- a/crates/sui-rpc-api/src/types.rs +++ b/crates/sui-rpc-api/src/types.rs @@ -37,10 +37,9 @@ pub const X_SUI_TIMESTAMP_MS: &str = "x-sui-timestamp-ms"; /// Basic information about the state of a Node #[serde_with::serde_as] -#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct NodeInfo { /// The chain identifier of the chain that this Node is on - #[schemars(with = "String")] pub chain_id: sui_sdk_types::CheckpointDigest, /// Human readable name of the chain that this Node is on @@ -48,28 +47,23 @@ pub struct NodeInfo { /// Current epoch of the Node based on its highest executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] - #[schemars(with = "crate::rest::_schemars::U64")] pub epoch: u64, /// Checkpoint height of the most recently executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] - #[schemars(with = "crate::rest::_schemars::U64")] pub checkpoint_height: u64, /// Unix timestamp of the most recently executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] - #[schemars(with = "crate::rest::_schemars::U64")] pub timestamp_ms: u64, /// The lowest checkpoint for which checkpoints and transaction data is available #[serde_as(as = "Option>")] - #[schemars(with = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub lowest_available_checkpoint: Option, /// The lowest checkpoint for which object data is available #[serde_as(as = "Option>")] - #[schemars(with = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub lowest_available_checkpoint_objects: Option, pub software_version: std::borrow::Cow<'static, str>, @@ -141,7 +135,7 @@ pub struct CheckpointResponse { pub contents_bcs: Option>, } -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct GetCheckpointOptions { /// Request `CheckpointSummary` be included in the response /// @@ -242,7 +236,7 @@ pub struct TransactionResponse { pub timestamp_ms: Option, } -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct GetTransactionOptions { /// Request `Transaction` be included in the response /// @@ -328,7 +322,7 @@ impl GetTransactionOptions { } /// Options for the execute transaction endpoint -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ExecuteTransactionOptions { /// Request `TransactionEffects` be included in the Response. /// @@ -432,7 +426,7 @@ pub enum EffectsFinality { QuorumExecuted, } -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct GetFullCheckpointOptions { /// Request `CheckpointSummary` be included in the response /// diff --git a/crates/sui-rpc-api/tests/openapi.rs b/crates/sui-rpc-api/tests/openapi.rs deleted file mode 100644 index 08172b7be894d..0000000000000 --- a/crates/sui-rpc-api/tests/openapi.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use sui_rpc_api::rest::info; -use sui_rpc_api::rest::openapi; -use sui_rpc_api::rest::ENDPOINTS; - -#[test] -fn openapi_spec() { - const OPENAPI_SPEC_FILE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/openapi/openapi.json"); - - let openapi = { - let mut api = openapi::Api::new(info("unknown")); - - api.register_endpoints(ENDPOINTS.iter().copied()); - api.openapi() - }; - - let mut actual = serde_json::to_string_pretty(&openapi).unwrap(); - actual.push('\n'); - - // Update the expected format - if std::env::var_os("UPDATE").is_some() { - std::fs::write(OPENAPI_SPEC_FILE, &actual).unwrap(); - } - - let expected = std::fs::read_to_string(OPENAPI_SPEC_FILE).unwrap(); - - let diff = diffy::create_patch(&expected, &actual); - - if !diff.hunks().is_empty() { - let formatter = if std::io::IsTerminal::is_terminal(&std::io::stderr()) { - diffy::PatchFormatter::new().with_color() - } else { - diffy::PatchFormatter::new() - }; - let header = "Generated and checked-in openapi spec does not match. \ - Re-run with `UPDATE=1` to update expected format"; - panic!("{header}\n\n{}", formatter.fmt_patch(&diff)); - } -} - -#[tokio::test] -async fn openapi_explorer() { - // Unless env var is set, just early return - if std::env::var_os("OPENAPI_EXPLORER").is_none() { - return; - } - - let openapi = { - let mut api = openapi::Api::new(info("unknown")); - api.register_endpoints(ENDPOINTS.to_owned()); - api.openapi() - }; - - let router = openapi::OpenApiDocument::new(openapi).into_router(); - - let listener = tokio::net::TcpListener::bind("127.0.0.1:8000") - .await - .unwrap(); - axum::serve(listener, router).await.unwrap(); -} diff --git a/scripts/update_all_snapshots.sh b/scripts/update_all_snapshots.sh index e6aa625397d4b..786f71bed5051 100755 --- a/scripts/update_all_snapshots.sh +++ b/scripts/update_all_snapshots.sh @@ -16,4 +16,3 @@ cd "$ROOT/crates/sui-swarm-config" && cargo insta test --review cd "$ROOT/crates/sui-open-rpc" && cargo run --example generate-json-rpc-spec -- record cd "$ROOT/crates/sui-core" && cargo run --example generate-format -- print > tests/staged/sui.yaml UPDATE=1 cargo test -p sui-framework --test build-system-packages -UPDATE=1 cargo test -p sui-rpc-api