209 lines
7.3 KiB
Rust
209 lines
7.3 KiB
Rust
#![allow(async_fn_in_trait)]
|
|
|
|
use api_macros::{
|
|
ApiContract, BinaryBody, HttpMethod, NoBody, Operation, ParameterLocation, TransportMetadata,
|
|
WebSocketOperation, WireKind, api,
|
|
};
|
|
|
|
pub struct CreateWidget;
|
|
pub struct Widget;
|
|
pub struct Lookup;
|
|
pub struct PublicError;
|
|
pub struct SearchResult<T>(std::marker::PhantomData<T>);
|
|
pub struct ClientFrame;
|
|
pub struct ServerFrame;
|
|
|
|
#[api]
|
|
pub trait MixedTransportApi {
|
|
#[get("/status")]
|
|
async fn status(&self) -> Widget;
|
|
|
|
#[websocket(
|
|
"/widgets/{widget_id}/events",
|
|
operation_id = "widgets.events",
|
|
method = GET,
|
|
client_to_server = ClientFrame,
|
|
server_to_client = ServerFrame,
|
|
path_parameters = [widget_id: u64]
|
|
)]
|
|
type WidgetEvents;
|
|
}
|
|
|
|
#[api]
|
|
pub trait WidgetApi {
|
|
#[post(
|
|
"/widgets",
|
|
operation_id = "widgets.create",
|
|
status = 201,
|
|
error_status = 422
|
|
)]
|
|
async fn create(
|
|
&self,
|
|
#[body] request: CreateWidget,
|
|
#[header("x-request-id")] request_id: String,
|
|
) -> Result<Widget, PublicError>;
|
|
|
|
#[get("/widgets/{widget_id}", operation_id = "widgets.get")]
|
|
async fn lookup(&self, widget_id: u64, #[query] query: Lookup) -> Result<Widget, PublicError>;
|
|
|
|
#[delete("/widgets/{widget_id}", operation_id = "widgets.delete")]
|
|
async fn delete(&self, #[path] widget_id: u64) -> ();
|
|
}
|
|
|
|
#[api]
|
|
pub trait WrapperApi {
|
|
#[get("/search", operation_id = "search")]
|
|
async fn search(&self) -> SearchResult<Widget>;
|
|
}
|
|
|
|
#[api]
|
|
pub trait BinaryApi {
|
|
#[put("/payload", operation_id = "payload.upload")]
|
|
async fn upload(&self, #[binary] payload: BinaryBody) -> Widget;
|
|
}
|
|
|
|
#[api]
|
|
pub trait ConditionalApi {
|
|
#[get(
|
|
"/conditional",
|
|
operation_id = "conditional.get",
|
|
responses = [
|
|
(status = 200, body = Widget, headers = [("etag", String), ("cache-control", String)]),
|
|
(status = 304, headers = [("etag", String), ("cache-control", String)])
|
|
]
|
|
)]
|
|
async fn fetch(&self) -> conditional_api_responses::Fetch;
|
|
}
|
|
|
|
#[api]
|
|
pub trait RawIdentifierApi {
|
|
#[get("/type", operation_id = "raw.type")]
|
|
async fn r#type(&self) -> Widget;
|
|
}
|
|
|
|
#[api]
|
|
pub trait UnusualIdentifierApi {
|
|
#[get("/underscores", operation_id = "unusual.underscores")]
|
|
async fn __(&self) -> Widget;
|
|
|
|
#[get("/underscore-digit", operation_id = "unusual.underscore_digit")]
|
|
async fn _0(&self) -> Widget;
|
|
}
|
|
|
|
fn assert_operation_types<O>()
|
|
where
|
|
O: Operation<
|
|
Parameters = (CreateWidget, String),
|
|
RequestBody = CreateWidget,
|
|
ResponseBody = Widget,
|
|
ErrorBody = PublicError,
|
|
>,
|
|
{
|
|
}
|
|
|
|
#[test]
|
|
fn expansion_exposes_deterministic_metadata_and_type_connections() {
|
|
assert_operation_types::<widget_api_operations::Create>();
|
|
|
|
fn assert_named_wrapper<O: Operation<ResponseBody = SearchResult<Widget>>>() {}
|
|
assert_named_wrapper::<wrapper_api_operations::Search>();
|
|
let raw = <raw_identifier_api_operations::Type as Operation>::METADATA;
|
|
assert_eq!(raw.operation_id, "raw.type");
|
|
let underscores = <unusual_identifier_api_operations::Operation5f5f as Operation>::METADATA;
|
|
assert_eq!(underscores.operation_id, "unusual.underscores");
|
|
let underscore_digit =
|
|
<unusual_identifier_api_operations::Operation5f30 as Operation>::METADATA;
|
|
assert_eq!(underscore_digit.operation_id, "unusual.underscore_digit");
|
|
|
|
let operations = <WidgetApiMetadata as ApiContract>::OPERATIONS;
|
|
assert_eq!(operations.len(), 3);
|
|
assert_eq!(operations[0].operation_id, "widgets.create");
|
|
assert_eq!(operations[1].operation_id, "widgets.delete");
|
|
assert_eq!(operations[2].operation_id, "widgets.get");
|
|
|
|
let create = <widget_api_operations::Create as Operation>::METADATA;
|
|
assert_eq!(operations[0], create);
|
|
assert_eq!(create.method, HttpMethod::Post);
|
|
assert_eq!(create.path, "/widgets");
|
|
assert_eq!(create.transport, TransportMetadata::Http);
|
|
assert_eq!(create.request_body.wire_kind, WireKind::Json);
|
|
assert_eq!(create.success_responses[0].status, 201);
|
|
assert_eq!(create.success_responses[0].body.wire_kind, WireKind::Json);
|
|
assert_eq!(create.error_responses[0].status, 422);
|
|
assert_eq!(create.parameters[0].location, ParameterLocation::Body);
|
|
assert_eq!(create.parameters[0].rust_type, "CreateWidget");
|
|
assert_eq!(create.parameters[1].location, ParameterLocation::Header);
|
|
assert_eq!(create.parameters[1].wire_name, "x-request-id");
|
|
|
|
let lookup = <widget_api_operations::Lookup as Operation>::METADATA;
|
|
assert_eq!(lookup.parameters[0].location, ParameterLocation::Path);
|
|
assert_eq!(lookup.parameters[1].location, ParameterLocation::Query);
|
|
|
|
let upload = <binary_api_operations::Upload as Operation>::METADATA;
|
|
assert_eq!(upload.request_body.wire_kind, WireKind::Binary);
|
|
assert_eq!(upload.parameters[0].location, ParameterLocation::Body);
|
|
fn assert_binary_request<O: Operation<RequestBody = BinaryBody>>() {}
|
|
assert_binary_request::<binary_api_operations::Upload>();
|
|
|
|
let conditional = <conditional_api_operations::Fetch as Operation>::METADATA;
|
|
assert_eq!(conditional.success_responses.len(), 2);
|
|
assert_eq!(conditional.success_responses[0].status, 200);
|
|
assert_eq!(
|
|
conditional.success_responses[0].body.wire_kind,
|
|
WireKind::Json
|
|
);
|
|
assert_eq!(conditional.success_responses[0].headers.len(), 2);
|
|
assert_eq!(
|
|
conditional.success_responses[0].headers[0].wire_name,
|
|
"etag"
|
|
);
|
|
assert_eq!(
|
|
conditional.success_responses[0].headers[0].rust_type,
|
|
"String"
|
|
);
|
|
assert_eq!(conditional.success_responses[1].status, 304);
|
|
assert_eq!(
|
|
conditional.success_responses[1].body.wire_kind,
|
|
WireKind::Empty
|
|
);
|
|
fn assert_conditional_response<
|
|
O: Operation<ResponseBody = conditional_api_responses::Fetch>,
|
|
>() {
|
|
}
|
|
assert_conditional_response::<conditional_api_operations::Fetch>();
|
|
|
|
let delete = <widget_api_operations::Delete as Operation>::METADATA;
|
|
assert_eq!(delete.success_responses[0].status, 204);
|
|
assert_eq!(delete.success_responses[0].body.wire_kind, WireKind::Empty);
|
|
assert!(delete.error_responses.is_empty());
|
|
|
|
fn assert_empty_request<O: Operation<RequestBody = NoBody>>() {}
|
|
fn assert_empty_response<O: Operation<ResponseBody = NoBody, ErrorBody = NoBody>>() {}
|
|
assert_empty_request::<widget_api_operations::Lookup>();
|
|
assert_empty_response::<widget_api_operations::Delete>();
|
|
|
|
fn assert_websocket_types<
|
|
O: WebSocketOperation<
|
|
PathParameters = (u64,),
|
|
ClientToServerFrame = ClientFrame,
|
|
ServerToClientFrame = ServerFrame,
|
|
>,
|
|
>() {
|
|
}
|
|
assert_websocket_types::<mixed_transport_api_operations::WidgetEvents>();
|
|
let websocket = <mixed_transport_api_operations::WidgetEvents as WebSocketOperation>::METADATA;
|
|
assert_eq!(websocket.method, HttpMethod::Get);
|
|
assert_eq!(websocket.path, "/widgets/{widget_id}/events");
|
|
assert_eq!(websocket.parameters[0].rust_name, "widget_id");
|
|
assert_eq!(websocket.parameters[0].rust_type, "u64");
|
|
assert!(websocket.success_responses.is_empty());
|
|
assert!(websocket.error_responses.is_empty());
|
|
assert_eq!(
|
|
websocket.transport,
|
|
TransportMetadata::WebSocket {
|
|
client_to_server_frame: "ClientFrame",
|
|
server_to_client_frame: "ServerFrame",
|
|
}
|
|
);
|
|
}
|