[hermes] add utoipa for API docs (#990)

* [hermes] add utoipa for docs

* fix build

* format
This commit is contained in:
Jayant Krishnamurthy 2023-07-27 11:29:56 -07:00 committed by GitHub
parent 9e4242a20e
commit 61e29ac166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 238 additions and 5 deletions

158
hermes/Cargo.lock generated
View File

@ -1173,6 +1173,15 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "2.0.0" version = "2.0.0"
@ -1183,6 +1192,17 @@ dependencies = [
"dirs-sys-next", "dirs-sys-next",
] ]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "dirs-sys-next" name = "dirs-sys-next"
version = "0.1.2" version = "0.1.2"
@ -1781,6 +1801,8 @@ dependencies = [
"strum", "strum",
"tokio", "tokio",
"tower-http", "tower-http",
"utoipa",
"utoipa-swagger-ui",
"wormhole-sdk", "wormhole-sdk",
] ]
@ -2070,6 +2092,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.0", "hashbrown 0.14.0",
"serde",
] ]
[[package]] [[package]]
@ -2969,6 +2992,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -4252,6 +4285,41 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "rust-embed"
version = "6.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
dependencies = [
"proc-macro2 1.0.66",
"quote 1.0.31",
"rust-embed-utils",
"shellexpand",
"syn 2.0.26",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
dependencies = [
"sha2 0.10.7",
"walkdir",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -4414,6 +4482,15 @@ dependencies = [
"cipher 0.4.4", "cipher 0.4.4",
] ]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.22" version = "0.1.22"
@ -4727,6 +4804,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "shellexpand"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4"
dependencies = [
"dirs",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -6057,6 +6143,15 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.13" version = "0.3.13"
@ -6157,6 +6252,47 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utoipa"
version = "3.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c624186f22e625eb8faa777cb33d34cd595aa16d1742aa1d8b6cf35d3e4dda9"
dependencies = [
"indexmap 2.0.0",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "3.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ce5f21ca77e010f5283fa791c6ab892c68b3668a1bdc6b7ac6cf978f5d5b30"
dependencies = [
"proc-macro-error",
"proc-macro2 1.0.66",
"quote 1.0.31",
"regex",
"syn 2.0.26",
]
[[package]]
name = "utoipa-swagger-ui"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4602d7100d3cfd8a086f30494e68532402ab662fa366c9d201d677e33cee138d"
dependencies = [
"axum",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"utoipa",
"zip",
]
[[package]] [[package]]
name = "value-bag" name = "value-bag"
version = "1.4.1" version = "1.4.1"
@ -6193,6 +6329,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@ -6669,6 +6815,18 @@ dependencies = [
"syn 2.0.26", "syn 2.0.26",
] ]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.11.2+zstd.1.5.2" version = "0.11.2+zstd.1.5.2"

View File

@ -59,6 +59,8 @@ structopt = { version = "0.3.26" }
strum = { version = "0.24.1", features = ["derive"] } strum = { version = "0.24.1", features = ["derive"] }
tokio = { version = "1.26.0", features = ["full"] } tokio = { version = "1.26.0", features = ["full"] }
tower-http = { version = "0.4.0", features = ["cors"] } tower-http = { version = "0.4.0", features = ["cors"] }
utoipa = { version = "3.4.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "3.1.4", features = ["axum"] }
wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" } wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }
[patch.crates-io] [patch.crates-io]

View File

@ -75,6 +75,8 @@ fn main() {
.output() .output()
.expect("failed to generate protobuf definitions"); .expect("failed to generate protobuf definitions");
let rust_target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
// Build the Go library. // Build the Go library.
let mut cmd = Command::new("go"); let mut cmd = Command::new("go");
cmd.arg("build") cmd.arg("build")
@ -84,11 +86,28 @@ fn main() {
.arg("src/network/p2p.go") .arg("src/network/p2p.go")
.arg("src/network/p2p.pb.go"); .arg("src/network/p2p.pb.go");
// Cross-compile the Go binary based on the Rust target architecture
match &*rust_target_arch {
"x86_64" => {
// CGO_ENABLED required for building amd64 on mac os
cmd.env("GOARCH", "amd64").env("CGO_ENABLED", "1");
}
"aarch64" => {
cmd.env("GOARCH", "arm64");
}
// Add other target architectures as needed.
_ => {}
}
// Tell Rust to link our Go library at compile time. // Tell Rust to link our Go library at compile time.
println!("cargo:rustc-link-search=native={out_var}"); println!("cargo:rustc-link-search=native={out_var}");
println!("cargo:rustc-link-lib=static=pythnet"); println!("cargo:rustc-link-lib=static=pythnet");
println!("cargo:rustc-link-lib=resolv"); println!("cargo:rustc-link-lib=resolv");
let status = cmd.status().unwrap(); let go_build_output = cmd.output().expect("Failed to execute Go build command");
assert!(status.success()); if !go_build_output.status.success() {
let error_message = String::from_utf8_lossy(&go_build_output.stderr);
panic!("Go build failed:\n{}", error_message);
}
} }

View File

@ -12,6 +12,16 @@ use {
sync::mpsc::Receiver, sync::mpsc::Receiver,
}, },
tower_http::cors::CorsLayer, tower_http::cors::CorsLayer,
utoipa::{
openapi::security::{
ApiKey,
ApiKeyValue,
SecurityScheme,
},
Modify,
OpenApi,
},
utoipa_swagger_ui::SwaggerUi,
}; };
mod rest; mod rest;
@ -38,12 +48,27 @@ impl State {
/// Currently this is based on Axum due to the simplicity and strong ecosystem support for the /// Currently this is based on Axum due to the simplicity and strong ecosystem support for the
/// packages they are based on (tokio & hyper). /// packages they are based on (tokio & hyper).
pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: String) -> Result<()> { pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: String) -> Result<()> {
#[derive(OpenApi)]
#[openapi(
paths(
rest::latest_price_feeds,
),
components(
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed)
),
tags(
(name = "hermes", description = "Pyth Real-Time Pricing API")
)
)]
struct ApiDoc;
let state = State::new(store); let state = State::new(store);
// Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the // Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
// `with_state` method which replaces `Body` with `State` in the type signature. // `with_state` method which replaces `Body` with `State` in the type signature.
let app = Router::new(); let app = Router::new();
let app = app let app = app
.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi()))
.route("/", get(rest::index)) .route("/", get(rest::index))
.route("/live", get(rest::live)) .route("/live", get(rest::live))
.route("/ready", get(rest::ready)) .route("/ready", get(rest::ready))

View File

@ -31,6 +31,7 @@ use {
pyth_sdk::PriceIdentifier, pyth_sdk::PriceIdentifier,
serde_qs::axum::QsQuery, serde_qs::axum::QsQuery,
std::collections::HashSet, std::collections::HashSet,
utoipa::IntoParams,
}; };
pub enum RestError { pub enum RestError {
@ -95,15 +96,32 @@ pub async fn latest_vaas(
)) ))
} }
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize, IntoParams)]
pub struct LatestPriceFeedsQueryParams { pub struct LatestPriceFeedsQueryParams {
#[param(value_type = String)]
ids: Vec<PriceIdInput>, ids: Vec<PriceIdInput>,
#[serde(default)] #[serde(default)]
#[param(value_type = Option<bool>, required = false, nullable = true)]
verbose: bool, verbose: bool,
#[serde(default)] #[serde(default)]
#[param(value_type = Option<bool>, required = false, nullable = true)]
binary: bool, binary: bool,
} }
/// Get the latest prices by price feed ids.
///
/// Get the latest price updates for a provided collection of price feed ids.
///
#[utoipa::path(
get,
path = "/api/latest_price_feeds",
responses(
(status = 200, description = "Price feeds retrieved successfully", body = [Vec<RpcPriceFeed>])
),
params(
LatestPriceFeedsQueryParams
)
)]
pub async fn latest_price_feeds( pub async fn latest_price_feeds(
State(state): State<super::State>, State(state): State<super::State>,
QsQuery(params): QsQuery<LatestPriceFeedsQueryParams>, QsQuery(params): QsQuery<LatestPriceFeedsQueryParams>,

View File

@ -19,6 +19,14 @@ use {
Price, Price,
PriceIdentifier, PriceIdentifier,
}, },
utoipa::{
openapi::{
RefOr,
Schema,
},
IntoParams,
ToSchema,
},
wormhole_sdk::Chain, wormhole_sdk::Chain,
}; };
@ -40,14 +48,16 @@ impl From<PriceIdInput> for PriceIdentifier {
type Base64String = String; type Base64String = String;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct RpcPriceFeedMetadata { pub struct RpcPriceFeedMetadata {
#[schema(value_type = u64)]
pub slot: Slot, pub slot: Slot,
pub emitter_chain: u16, pub emitter_chain: u16,
#[schema(value_type = i64)]
pub price_service_receive_time: UnixTimestamp, pub price_service_receive_time: UnixTimestamp,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct RpcPriceFeed { pub struct RpcPriceFeed {
pub id: PriceIdentifier, pub id: PriceIdentifier,
pub price: Price, pub price: Price,
@ -56,6 +66,7 @@ pub struct RpcPriceFeed {
pub metadata: Option<RpcPriceFeedMetadata>, pub metadata: Option<RpcPriceFeedMetadata>,
/// Vaa binary represented in base64. /// Vaa binary represented in base64.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[schema(value_type = Option<String>)]
pub vaa: Option<Base64String>, pub vaa: Option<Base64String>,
} }