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
9 changes: 3 additions & 6 deletions examples/client/market_maker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().skip(1).collect();

if args.len() != 2 {
eprintln!("Unexpected command line arguments. Received {:?}", args);
eprintln!("Unexpected command line arguments. Received {args:?}");
eprintln!("Usage: example-client quay-server wallet_address");
exit(1);
}
Expand Down Expand Up @@ -170,7 +170,7 @@ async fn setup(quay_uri: Uri, private_key: String) -> (String, Address) {
match response {
Ok(_) => (),
Err(error) => {
eprintln!("Unable to verify client. Reported error:\n{:?}", error);
eprintln!("Unable to verify client. Reported error:\n{error:?}");
exit(2);
}
}
Expand All @@ -180,10 +180,7 @@ async fn setup(quay_uri: Uri, private_key: String) -> (String, Address) {
match response {
Ok(_) => (),
Err(error) => {
eprintln!(
"Unable to check authentication with Quay. Reported error:\n{:?}",
error
);
eprintln!("Unable to check authentication with Quay. Reported error:\n{error:?}");
exit(3);
}
}
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
3 changes: 1 addition & 2 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ impl TryFrom<String> for Environment {
"local" => Ok(Self::Local),
"production" => Ok(Self::Production),
other => Err(format!(
"{} is not a supported environment. Use either `local` or `production`.",
other
"{other} is not a supported environment. Use either `local` or `production`."
)),
}
}
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
15 changes: 7 additions & 8 deletions src/services/rfq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Rfq for RFQService {
}
}
Err(error) => {
eprintln!("RFQService:WebTaker: Error while receiving broadcast requests. Error reported\n{}", error);
eprintln!("RFQService:WebTaker: Error while receiving broadcast requests. Error reported\n{error}");
tx_trader
.send(Err(Status::internal("Internal server error 2")))
.await
Expand All @@ -84,8 +84,7 @@ impl Rfq for RFQService {
}
Err(error) => {
eprintln!(
"RFQService:WebTaker: Error while broadcasting request. Error reported\n{}",
error
"RFQService:WebTaker: Error while broadcasting request. Error reported\n{error}"
);
tx_trader
.send(Err(Status::internal("Internal server error 1")))
Expand Down Expand Up @@ -144,7 +143,7 @@ impl Rfq for RFQService {
}
},
Err(error) => {
eprintln!("RFQService:Taker: Error while handling taker stream. Reported error\n{}", error);
eprintln!("RFQService:Taker: Error while handling taker stream. Reported error\n{error}");
stream_closed = true;
}
}
Expand All @@ -162,7 +161,7 @@ impl Rfq for RFQService {
}
},
Err(error) => {
eprintln!("RFQService:Taker: Error while reading response broadcast stream. Reported error\n{}", error);
eprintln!("RFQService:Taker: Error while reading response broadcast stream. Reported error\n{error}");
tx_taker.send(Err(Status::internal("Internal server error 3"))).await.unwrap_or_default();
stream_closed = true;
}
Expand Down Expand Up @@ -209,7 +208,7 @@ impl Rfq for RFQService {
match request {
Ok(request) => tx_maker.send(Ok(request)).await.unwrap(),
Err(error) => {
eprintln!("RFQService:Maker: Request stream has closed. Reported error\n{:?}", error);
eprintln!("RFQService:Maker: Request stream has closed. Reported error\n{error:?}");
tx_maker.send(Err(Status::internal("Internal server error 4"))).await.unwrap_or_default();
stream_closed = true;
}
Expand All @@ -222,7 +221,7 @@ impl Rfq for RFQService {
match response_tx_stream.send(response) {
Ok(_) => (),
Err(error) => {
eprintln!("RFQService:Maker: All response tx stream receivers have been dropped. Error reported\n{}", error);
eprintln!("RFQService:Maker: All response tx stream receivers have been dropped. Error reported\n{error}");
tx_maker.send(Err(Status::internal("Internal server error 5"))).await.unwrap_or_default();
stream_closed = true;
}
Expand All @@ -233,7 +232,7 @@ impl Rfq for RFQService {
stream_closed = true;
},
Err(error) => {
eprintln!("RFQService:Maker: Error while handling maker stream. Reported error\n{:?}", error);
eprintln!("RFQService:Maker: Error while handling maker stream. Reported error\n{error:?}");
stream_closed = true;
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/services/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ impl Session for SessionService {
Ok(_) => (),
Err(error) => {
return Err(Status::unauthenticated(format!(
"Invalid signature {:?}.",
error
"Invalid signature {error:?}."
)))
}
}
Expand Down
Loading