Skip to content

Commit

Permalink
server: Clean up openapi module (#1527)
Browse files Browse the repository at this point in the history
  • Loading branch information
svix-jplatte authored Nov 20, 2024
2 parents 38130cc + 6797d39 commit 7e68e1b
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 30 deletions.
2 changes: 1 addition & 1 deletion server/svix-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pub async fn run_with_prefix(
.nest_api_service("/api/v1", v1_router)
.finish_api(&mut openapi);

let openapi = openapi::postprocess_spec(openapi);
openapi::postprocess_spec(&mut openapi);
let docs_router = docs::router(openapi);
let app = app.merge(docs_router).layer((
layer_fn(move |service| IdempotencyService {
Expand Down
2 changes: 1 addition & 1 deletion server/svix-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ async fn main() -> anyhow::Result<()> {
.nest("/api/v1", router)
.finish_api(&mut openapi);

let openapi = svix_server::openapi::postprocess_spec(openapi);
svix_server::openapi::postprocess_spec(&mut openapi);
println!(
"{}",
serde_json::to_string_pretty(&openapi).expect("Failed to serialize JSON spec")
Expand Down
46 changes: 18 additions & 28 deletions server/svix-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub fn initialize_openapi() -> OpenApi {

/// Replaces OpenAPI 3.1 style `"foo": true` schemas with OpenAPI 3.0 style
/// `"foo": {"type": "object"}` schemas.
fn replace_true_schemas(mut openapi: OpenApi) -> OpenApi {
fn replace_true_schemas(openapi: &mut OpenApi) {
use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec};

// Checks if it's a plain boolean schema, and if yes replaces it with a
Expand Down Expand Up @@ -140,24 +140,22 @@ fn replace_true_schemas(mut openapi: OpenApi) -> OpenApi {

fn visit_object_validation(obj: &mut Box<ObjectValidation>) {
if let Some(additional_props) = &mut obj.additional_properties {
visit_schema(additional_props.as_mut())
visit_schema(additional_props)
}
for (_, schema) in obj.properties.iter_mut() {
for (_, schema) in &mut obj.properties {
visit_schema(schema)
}
}

if let Some(ref mut components) = openapi.components {
for (_, schema) in components.schemas.iter_mut() {
if let Some(components) = &mut openapi.components {
for (_, schema) in &mut components.schemas {
visit_schema(&mut schema.json_schema)
}
}

openapi
}

/// Adds the `Idempotency-Key` header parameter to all `POST` operations in the schema.
fn add_idempotency_to_post(mut openapi: OpenApi) -> OpenApi {
fn add_idempotency_to_post(openapi: &mut OpenApi) {
// The header's value can be any valid string
let string_schema = aide::gen::in_context(|ctx| String::json_schema(&mut ctx.schema));

Expand All @@ -179,8 +177,8 @@ fn add_idempotency_to_post(mut openapi: OpenApi) -> OpenApi {
extensions: indexmap::indexmap! {},
};

if let Some(paths) = openapi.paths.as_mut() {
for (_, op) in paths.paths.iter_mut() {
if let Some(paths) = &mut openapi.paths {
for (_, op) in &mut paths.paths {
match op {
openapi::ReferenceOr::Reference { reference, .. } => {
// References to operations should never appear in our
Expand All @@ -191,55 +189,49 @@ fn add_idempotency_to_post(mut openapi: OpenApi) -> OpenApi {
);
}
openapi::ReferenceOr::Item(op) => {
if let Some(post) = op.post.as_mut() {
if let Some(post) = &mut op.post {
post.parameters.push(ReferenceOr::Item(Parameter::Header {
parameter_data: idempotency_key_data.clone(),
style: openapi::HeaderStyle::Simple,
}))
}));
}
}
}
}
}

openapi
}

/// Remove schemas from `components.schemas` of the spec which are under normal
/// circumstances not referenced. At the moment these are struct schemas used
/// by query parameters and path placeholders.
fn remove_unneeded_schemas(mut openapi: OpenApi) -> OpenApi {
if let Some(components) = openapi.components.as_mut() {
fn remove_unneeded_schemas(openapi: &mut OpenApi) {
if let Some(components) = &mut openapi.components {
components.schemas.retain(|name, _| {
!(name.ends_with("Path")
|| name.ends_with("QueryParams")
|| name.starts_with("Pagination"))
});
};

openapi
}
}

/// Replaces the `examples` property of a schema with a singular `example`
/// property.
/// OpenAPI <=3.0 used `example` as an extension, >=3.1 standardized `examples`.
fn replace_multiple_examples(mut openapi: OpenApi) -> OpenApi {
fn replace_multiple_examples(openapi: &mut OpenApi) {
let mut visitor = schemars::visit::SetSingleExample {
retain_examples: false,
};

if let Some(components) = openapi.components.as_mut() {
for (_, schema_object) in components.schemas.iter_mut() {
if let Some(components) = &mut openapi.components {
for (_, schema_object) in &mut components.schemas {
visitor.visit_schema(&mut schema_object.json_schema);
}
}

openapi
}

/// Applies a list of hacks to the finished OpenAPI spec to make it usable with
/// our tooling.
pub fn postprocess_spec(mut openapi: OpenApi) -> OpenApi {
pub fn postprocess_spec(openapi: &mut OpenApi) {
let hacks = [
add_idempotency_to_post,
remove_unneeded_schemas,
Expand All @@ -248,10 +240,8 @@ pub fn postprocess_spec(mut openapi: OpenApi) -> OpenApi {
];

for hack in hacks {
openapi = hack(openapi);
hack(openapi);
}

openapi
}

/// This module documents operational webhooks. To document one define a data
Expand Down

0 comments on commit 7e68e1b

Please sign in to comment.