Complete repository contract error coverage
This commit is contained in:
@@ -52,7 +52,7 @@ pub fn status(code: u16) -> StatusCode {
|
||||
pub mod framework {
|
||||
pub use axum::{
|
||||
Json, Router,
|
||||
extract::{Extension, Path, Query, State},
|
||||
extract::{Extension, Path, Query, State, rejection::JsonRejection},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::Response,
|
||||
routing::{delete, get, head, options, patch, post, put},
|
||||
|
||||
@@ -39,7 +39,11 @@
|
||||
//! method name but should be set explicitly for contracts which must remain stable while Rust
|
||||
//! names evolve. Success status defaults to `200`, except an empty (`()`) response defaults to
|
||||
//! `204`. `alternate_status` opts a JSON response into [`HttpSuccess`] when two successful HTTP
|
||||
//! outcomes share one schema. A public error inferred from `Result<T, E>` defaults to status `400`.
|
||||
//! outcomes share one schema. A public error inferred from `Result<T, E>` defaults to status `400`;
|
||||
//! `additional_error_statuses` publishes the same typed error schema for other declared outcomes.
|
||||
//! `bearer_auth = true` and `browser_auth = true` attach standard bearer and browser-session cookie
|
||||
//! security schemes. Body operations may opt into typed Axum rejection normalization with
|
||||
//! typed Axum rejection normalization with `normalize_body_errors = true` and [`HttpRequestError`].
|
||||
//!
|
||||
//! Arguments are classified with `#[body]`, `#[query]`, `#[header]`, `#[path]`, or `#[extension]`.
|
||||
//! Extension values are trusted server-local Axum context: generated clients and OpenAPI omit them.
|
||||
@@ -211,6 +215,11 @@ pub trait HttpSuccess {
|
||||
fn status_code(&self) -> u16;
|
||||
}
|
||||
|
||||
/// Public error type capable of normalizing Axum request-body rejections.
|
||||
pub trait HttpRequestError: HttpError {
|
||||
fn from_request_rejection(status: u16, message: String) -> Self;
|
||||
}
|
||||
|
||||
/// Marker used when an operation has no request, response, or public error body.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum NoBody {}
|
||||
|
||||
@@ -131,6 +131,7 @@ pub struct OpenApiBuilder {
|
||||
schemas: BTreeMap<String, Value>,
|
||||
operation_ids: BTreeSet<String>,
|
||||
uses_bearer_auth: bool,
|
||||
uses_browser_auth: bool,
|
||||
}
|
||||
|
||||
impl OpenApiBuilder {
|
||||
@@ -157,6 +158,7 @@ impl OpenApiBuilder {
|
||||
schemas: BTreeMap::new(),
|
||||
operation_ids: BTreeSet::new(),
|
||||
uses_bearer_auth: false,
|
||||
uses_browser_auth: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -199,6 +201,7 @@ impl OpenApiBuilder {
|
||||
request_body: None,
|
||||
responses: BTreeMap::new(),
|
||||
bearer_security_required: None,
|
||||
browser_security_required: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -214,15 +217,30 @@ impl OpenApiBuilder {
|
||||
let schemas = self.schemas.into_iter().collect::<Map<_, _>>();
|
||||
let mut components = Map::new();
|
||||
components.insert("schemas".to_owned(), Value::Object(schemas));
|
||||
let mut security_schemes = Map::new();
|
||||
if self.uses_bearer_auth {
|
||||
security_schemes.insert(
|
||||
"bearerAuth".to_owned(),
|
||||
json!({
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}),
|
||||
);
|
||||
}
|
||||
if self.uses_browser_auth {
|
||||
security_schemes.insert(
|
||||
"browserSession".to_owned(),
|
||||
json!({
|
||||
"type": "apiKey",
|
||||
"in": "cookie",
|
||||
"name": "yoi_workspace_session"
|
||||
}),
|
||||
);
|
||||
}
|
||||
if !security_schemes.is_empty() {
|
||||
components.insert(
|
||||
"securitySchemes".to_owned(),
|
||||
json!({
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}
|
||||
}),
|
||||
Value::Object(security_schemes),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,6 +313,7 @@ pub struct OpenApiOperation<'a> {
|
||||
request_body: Option<Value>,
|
||||
responses: BTreeMap<String, Value>,
|
||||
bearer_security_required: Option<bool>,
|
||||
browser_security_required: bool,
|
||||
}
|
||||
|
||||
impl OpenApiOperation<'_> {
|
||||
@@ -338,6 +357,16 @@ impl OpenApiOperation<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bearer_authentication(&mut self) {
|
||||
self.parent.uses_bearer_auth = true;
|
||||
self.bearer_security_required = Some(true);
|
||||
}
|
||||
|
||||
pub fn browser_authentication(&mut self) {
|
||||
self.parent.uses_browser_auth = true;
|
||||
self.browser_security_required = true;
|
||||
}
|
||||
|
||||
pub fn request_body<T: OpenApiSchema>(
|
||||
&mut self,
|
||||
content_type: &'static str,
|
||||
@@ -418,15 +447,18 @@ impl OpenApiOperation<'_> {
|
||||
if let Some(request_body) = self.request_body {
|
||||
operation.insert("requestBody".to_owned(), request_body);
|
||||
}
|
||||
if let Some(required) = self.bearer_security_required {
|
||||
operation.insert(
|
||||
"security".to_owned(),
|
||||
if required {
|
||||
json!([{ "bearerAuth": [] }])
|
||||
} else {
|
||||
json!([{}, { "bearerAuth": [] }])
|
||||
},
|
||||
);
|
||||
let mut security = Vec::new();
|
||||
if self.bearer_security_required == Some(false) {
|
||||
security.push(json!({}));
|
||||
}
|
||||
if self.bearer_security_required.is_some() {
|
||||
security.push(json!({ "bearerAuth": [] }));
|
||||
}
|
||||
if self.browser_security_required {
|
||||
security.push(json!({ "browserSession": [] }));
|
||||
}
|
||||
if !security.is_empty() {
|
||||
operation.insert("security".to_owned(), Value::Array(security));
|
||||
}
|
||||
self.parent
|
||||
.paths
|
||||
|
||||
Reference in New Issue
Block a user