road-runner-common 0.9.0

Shared Rust utilities for exchange ecosystem backend services.
Documentation
#![cfg(feature = "openapi")]

use road_runner_common::openapi::GlobalErrorResponses;
use utoipa::openapi::path::{OperationBuilder, PathItem, PathItemType};
use utoipa::openapi::content::ContentBuilder;
use utoipa::openapi::response::ResponseBuilder;
use utoipa::openapi::schema::{ObjectBuilder, Schema, SchemaType};
use utoipa::openapi::{OpenApi, RefOr};
use utoipa::Modify;

fn openapi_with_plain_200() -> OpenApi {
    let mut openapi = OpenApi::new(utoipa::openapi::Info::new("test", "1.0"), utoipa::openapi::Paths::new());
    let operation = OperationBuilder::new()
        .response("200", ResponseBuilder::new().description("ok").build())
        .build();
    let path_item = PathItem::new(PathItemType::Get, operation);
    openapi.paths.paths.insert("/orders".to_string(), path_item);
    openapi
}

fn openapi_with_json_200(example: Option<serde_json::Value>) -> OpenApi {
    let mut openapi = OpenApi::new(utoipa::openapi::Info::new("test", "1.0"), utoipa::openapi::Paths::new());
    let mut content = ContentBuilder::new()
        .schema(Schema::from(
            ObjectBuilder::new()
                .property("id", ObjectBuilder::new().schema_type(SchemaType::String).build())
                .build(),
        ))
        .build();
    content.example = example;
    let operation = OperationBuilder::new()
        .response(
            "200",
            ResponseBuilder::new()
                .description("ok")
                .content("application/json", content)
                .build(),
        )
        .build();
    let path_item = PathItem::new(PathItemType::Get, operation);
    openapi.paths.paths.insert("/orders".to_string(), path_item);
    openapi
}

#[test]
fn global_error_responses_adds_schema_and_standard_errors() {
    // Arrange
    let mut openapi = openapi_with_plain_200();

    // Act
    GlobalErrorResponses.modify(&mut openapi);

    // Assert
    let components = openapi.components.unwrap();
    assert!(components.schemas.contains_key("ApiErrorResponse"));
    let operation = openapi.paths.paths.get("/orders").unwrap().operations.get(&PathItemType::Get).unwrap();
    for code in ["400", "401", "403", "404", "500", "503"] {
        assert!(operation.responses.responses.contains_key(code));
    }
}

#[test]
fn global_error_responses_preserves_existing_error_response() {
    // Arrange
    let mut openapi = openapi_with_plain_200();
    let operation = openapi.paths.paths.get_mut("/orders").unwrap().operations.get_mut(&PathItemType::Get).unwrap();
    operation.responses.responses.insert(
        "404".to_string(),
        ResponseBuilder::new().description("custom not found").build().into(),
    );

    // Act
    GlobalErrorResponses.modify(&mut openapi);

    // Assert
    let operation = openapi.paths.paths.get("/orders").unwrap().operations.get(&PathItemType::Get).unwrap();
    let response = operation.responses.responses.get("404").unwrap();
    let RefOr::T(response) = response else {
        panic!("expected inline response");
    };
    assert_eq!(response.description, "custom not found");
}

#[test]
fn global_error_responses_wraps_200_json_schema_and_existing_example() {
    // Arrange
    let mut openapi = openapi_with_json_200(Some(serde_json::json!({"id": "ord-1"})));

    // Act
    GlobalErrorResponses.modify(&mut openapi);

    // Assert
    let operation = openapi.paths.paths.get("/orders").unwrap().operations.get(&PathItemType::Get).unwrap();
    let response = operation.responses.responses.get("200").unwrap();
    let RefOr::T(response) = response else {
        panic!("expected inline response");
    };
    let content = response.content.get("application/json").unwrap();
    assert_eq!(content.example.as_ref().unwrap()["success"], true);
    assert_eq!(content.example.as_ref().unwrap()["data"]["id"], "ord-1");
    assert_eq!(content.example.as_ref().unwrap()["errors"], serde_json::json!([]));
}

#[test]
fn global_error_responses_wraps_200_json_schema_with_generic_example_when_missing() {
    // Arrange
    let mut openapi = openapi_with_json_200(None);

    // Act
    GlobalErrorResponses.modify(&mut openapi);

    // Assert
    let operation = openapi.paths.paths.get("/orders").unwrap().operations.get(&PathItemType::Get).unwrap();
    let response = operation.responses.responses.get("200").unwrap();
    let RefOr::T(response) = response else {
        panic!("expected inline response");
    };
    let content = response.content.get("application/json").unwrap();
    assert_eq!(content.example.as_ref().unwrap()["success"], true);
    assert_eq!(content.example.as_ref().unwrap()["data"], serde_json::Value::Null);
}