Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added utoipa for automatic OpenAPI spec generation #90

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ tracing-log = "0.1.3"
tracing-subscriber = { version = "0.3.16", features = ["registry", "env-filter"] }
ulid = "1.0.0"
uuid = { version = "1.2.2", features = ["v4", "serde"] }
utoipa = "2"

# TODO(sqlx breaks connect_timeout on minor version upgrade, violating semantic versioning)
[dependencies.sqlx]
Expand Down
3 changes: 2 additions & 1 deletion src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ethers::abi::ethereum_types::Signature;
use http::StatusCode;
use serde::{Deserialize, Serialize};
use siwe::Message;
use utoipa::IntoParams;

pub const NONCE_KEY: &str = "nonce";
pub const EXPIRATION_TIME_KEY: &str = "expirationTime";
Expand All @@ -18,7 +19,7 @@ pub fn unix_timestamp() -> Result<u64, anyhow::Error> {

// EIP-4361 based session

#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, IntoParams)]
pub struct SignedMessage {
pub signature: Signature,
pub message: Message,
Expand Down
10 changes: 10 additions & 0 deletions src/routes/health_check.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
use axum::response::IntoResponse;
use http::StatusCode;

/// Health check
///
/// Check if API is online
#[utoipa::path(
get,
path = "/health_check",
responses(
(status = 200, description = "API is online")
)
)]
pub async fn health_check() -> impl IntoResponse {
StatusCode::OK
}
11 changes: 11 additions & 0 deletions src/routes/metrics/prometheus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ use prometheus::Encoder;

use crate::telemetry::get_metrics_registry;

/// Metrics prometheus
///
/// Get prometheus metrics
#[utoipa::path(
get,
path = "/metrics/prometheus",
responses(
(status = 200, description = "Get prometheus matrics"),
(status = 500, description = "Failed to encode matrics"),
)
)]
pub async fn metrics_prometheus() -> impl IntoResponse {
let prometheus_storage_registry = get_metrics_registry();
let encoder = prometheus::TextEncoder::new();
Expand Down
12 changes: 12 additions & 0 deletions src/routes/nft_market/create_listing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ use crate::{
};
use crate::{database::save_order, structs::OrderInput};

/// Create listing
///
/// Create a new listing
#[utoipa::path(
post,
path = "/listings",
request_body = OrderInput,
responses(
(status = 200, description = "Create listing successfully"),
(status = 500, description = "Failed to create listing")
)
)]
#[tracing::instrument(
name = "Adding a new listing",
skip(db_pool, seaport, session, listing),
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
12 changes: 12 additions & 0 deletions src/routes/nft_market/create_offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ use crate::{
database::{save_address, save_consideration, save_offer, save_order},
};

/// Create offer
///
/// Create offer on listing
#[utoipa::path(
post,
path = "/offers",
request_body = OrderInput,
responses(
(status = 200, description = "Create offer successfully"),
(status = 500, description = "Failed to create offer")
)
)]
#[tracing::instrument(
name = "Adding a new offer",
skip(session, offer, db_pool, seaport),
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
14 changes: 14 additions & 0 deletions src/routes/nft_market/retrieve_listings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ use sqlx::{query_as, PgPool};

use crate::structs::{DBConsideration, DBOffer, DBOrder, OrderQuery, RetrieveResponse};

/// Retrieve listings
///
/// Retrieve listings from database with query
#[utoipa::path(
get,
path="/listings",
params(
OrderQuery
),
responses(
(status = 200, description = "Successfully retrieved listings", body=[RetrieveResponse]),
(status = 500, description = "Failed to retieve listings")
)
)]
pub async fn retrieve_listings(
State(pool): State<PgPool>,
query: Query<OrderQuery>,
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
14 changes: 14 additions & 0 deletions src/routes/nft_market/retrieve_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ use sqlx::{query_as, PgPool};

use crate::structs::{DBConsideration, DBOffer, DBOrder, OrderQuery, RetrieveResponse};

/// Retrieve offers
///
/// Retrieve offers from database with query
#[utoipa::path(
get,
path="/offers",
params(
OrderQuery
),
responses(
(status = 200, description = "Successfully retrieved offers", body = [RetrieveResponse]),
(status = 500, description = "Failed to retrieve offers")
),
)]
pub async fn retrieve_offers(
State(pool): State<PgPool>,
query: Query<OrderQuery>,
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
33 changes: 33 additions & 0 deletions src/routes/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ use siwe::VerificationOpts;

use crate::auth::*;

#[utoipa::path(
get,
path = "/nonce",
responses(
(status = 200, description = "Get nonce for session", body = Stirng),
(status = 500, description = "Failed to get nonce")
)
)]
#[tracing::instrument(name = "Getting an EIP-4361 nonce for session", skip(session))]
pub async fn get_nonce(mut session: WritableSession) -> impl IntoResponse {
let nonce = siwe::generate_nonce();
Expand Down Expand Up @@ -44,6 +52,19 @@ pub async fn get_nonce(mut session: WritableSession) -> impl IntoResponse {
(headers, nonce).into_response()
}
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved

/// Verify
///
/// Verify
#[utoipa::path(
post,
path = "/verify",
request_body = SignedMessage,
responses(
(status = 200, description = "Successfully verified"),
(status = 422, description = "Failed to get nonce or failed to validate signature"),
(status = 500, description = "Failed to varify")
)
)]
#[tracing::instrument(
name = "Verifying user EIP-4361 session",
skip(session, signed_message)
Expand Down Expand Up @@ -114,6 +135,18 @@ pub async fn verify(
(StatusCode::OK).into_response()
}
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved

/// Authenticate
///
/// Verify session
#[utoipa::path(
get,
path = "/authenticate",
responses(
(status = 200, description = "Successfully verified session"),
(status = 401, description = "Failed to verify session"),
(status = 500, description = "Fialed to verify session")
)
)]
#[tracing::instrument(name = "Checking user EIP-4361 authentication", skip(session))]
pub async fn authenticate(session: ReadableSession) -> impl IntoResponse {
verify_session(&session).await
Tevz-Beskovnik marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
31 changes: 30 additions & 1 deletion src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ use tonic::transport::Server;
use tower::{make::Shared, steer::Steer, BoxError, ServiceExt};
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use utoipa::OpenApi;

use crate::middleware::{track_prometheus_metrics, RequestIdLayer};
use crate::redis_pool::RedisConnectionManager;
use crate::rfq::rfq_server::RfqServer;
use crate::routes::*;
use crate::services::*;
use crate::session::session_server::SessionServer;
use crate::structs::RetrieveResponse;
use crate::{bindings::Seaport, state::AppState};
use crate::{
configuration::{DatabaseSettings, Settings},
Expand All @@ -51,6 +53,29 @@ pub fn run(
session_layer: SessionLayer<RedisSessionStore>,
rpc: Provider<Http>,
) -> BoxFuture<'static, Result<(), std::io::Error>> {
#[derive(OpenApi)]
#[openapi(
paths(
health_check,
metrics_prometheus,
create_listing,
retrieve_listings,
create_offer,
retrieve_offers,
get_nonce,
verify,
authenticate
),
components(
schemas(RetrieveResponse)
),
tags(
(name = "quay", description = "Quay is an open source, high performance backend for the Seaport smart
contracts")
)
)]
struct ApiDoc;

let provider = Arc::new(rpc.clone());

let seaport = Seaport::new(
Expand Down Expand Up @@ -78,6 +103,10 @@ pub fn run(
.route("/nonce", get(get_nonce))
.route("/verify", post(verify))
.route("/authenticate", get(authenticate))
.route(
"/spec/v3",
get(|| async { ApiDoc::openapi().to_json().unwrap() }),
)
// Layers/middleware
.layer(TraceLayer::new_for_http().make_span_with(TowerMakeSpanWithConstantId))
.layer(RequestIdLayer)
Expand Down Expand Up @@ -144,7 +173,7 @@ impl Application {
configuration.application.host, configuration.application.port
);

let listener = TcpListener::bind(&address)?;
let listener = TcpListener::bind(address)?;
let port = listener.local_addr().unwrap().port();

let store = RedisSessionStore::new(redis_multiplexed.clone(), Some("/sessions".into()));
Expand Down
7 changes: 4 additions & 3 deletions src/structs/seaport.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ethers::types::{H160, U256};
use serde::{Deserialize, Deserializer, Serialize};
use utoipa::{IntoParams, ToSchema};

use crate::bindings::seaport::{ConsiderationItem, OfferItem, Order, OrderComponents};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, IntoParams)]
pub struct OrderQuery {
#[serde(default)]
pub asset_contract_address: H160,
Expand All @@ -14,7 +15,7 @@ pub struct OrderQuery {
pub limit: Option<i64>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct RetrieveResponse {
pub orders: Vec<ComplexOrder>,
}
Expand Down Expand Up @@ -112,7 +113,7 @@ pub struct OrderInputParameters {
pub nonce: u64,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, IntoParams)]
#[serde(rename_all = "camelCase")]
pub struct OrderInput {
pub parameters: OrderInputParameters,
Expand Down