#![allow(clippy::result_large_err)]
pub(crate) mod adapters;
pub(crate) mod bootstrap;
pub mod ports;
mod config;
mod error;
mod handler_error;
mod request_id;
pub mod etag;
pub mod extract;
pub mod pagination;
#[cfg(feature = "testing")]
pub mod testing;
pub use bootstrap::{BootstrapCtx, ServiceBootstrap, ShutdownHookFn};
pub use config::{BootstrapConfig, CorsConfig, LogFormat, RateLimitConfig, RateLimitKind};
pub use error::{Error, Result};
pub use etag::{ETag, IfMatch, IfNoneMatch, check_if_match, etag_from_updated_at};
pub use handler_error::{
ApiError, CreatedResult, ErrorCode, HandlerError, HandlerResult, ProblemJson, ValidationError,
};
pub use pagination::{
CursorPaginatedResponse, CursorPagination, CursorPaginationParams, KeysetPaginatedResponse,
KeysetPaginationParams, PaginatedResponse, PaginationParams, SortDirection, SortParams,
};
#[cfg(feature = "cursor")]
pub use pagination::{Cursor, CursorError};
#[cfg(feature = "ratelimit")]
pub use adapters::security::rate_limit::{RateLimitBackend, RateLimitExtractor};
#[cfg(feature = "validation")]
pub use extract::Valid;
pub use ports::auth::AuthProvider;
pub use ports::health::ReadinessCheckFn;
pub use ports::rate_limit::RateLimitProvider;
#[cfg(feature = "telemetry")]
pub use ports::telemetry::{BasicTelemetryProvider, TelemetryProvider};
pub use api_bones::common::{ResourceId, Timestamp};
pub use api_bones::ratelimit::RateLimitInfo;
pub use api_bones::AuditInfo;
pub use api_bones::{ApiResponse, ApiResponseBuilder, ResponseMeta};
pub use api_bones::{BulkItemResult, BulkRequest, BulkResponse};
pub use api_bones::{CorrelationId, CorrelationIdError};
pub use api_bones::{IdempotencyKey, IdempotencyKeyError};
pub use api_bones::{Link, Links};
pub use api_bones::{RequestId, RequestIdParseError};
pub use api_bones::{Slug, SlugError};
#[cfg(feature = "openapi")]
#[macro_export]
macro_rules! generate_openapi_binary {
($api_doc:ty) => {
$crate::generate_openapi_binary!($api_doc, "/health");
};
($api_doc:ty, $health_path:expr) => {
fn main() {
use utoipa::OpenApi as _;
use $crate::openapi::{merge_health_paths, to_3_0_pretty_json};
let api = merge_health_paths(<$api_doc>::openapi(), $health_path);
match to_3_0_pretty_json(api) {
Ok(json) => println!("{json}"),
Err(e) => {
eprintln!("generate-openapi: serialize failed: {e}");
std::process::exit(1);
}
}
}
};
}
#[cfg(feature = "openapi")]
pub mod openapi {
pub fn merge_health_paths(
api: utoipa::openapi::OpenApi,
health_path: &str,
) -> utoipa::openapi::OpenApi {
crate::adapters::openapi::merge_health_paths(api, health_path)
}
pub fn rewrite_nullable_for_progenitor(json: String) -> String {
crate::adapters::openapi::rewrite_nullable_for_progenitor(json)
}
pub fn to_3_0_pretty_json(api: utoipa::openapi::OpenApi) -> serde_json::Result<String> {
crate::adapters::openapi::to_3_0_pretty_json(api)
}
pub fn strip_non_success_response_content(val: &mut serde_json::Value) {
crate::adapters::openapi::strip_non_success_response_content(val)
}
pub use crate::generate_openapi_binary;
pub struct BearerAuthAddon;
impl utoipa::Modify for BearerAuthAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
let components = openapi.components.get_or_insert_with(Default::default);
components.add_security_scheme(
"bearerAuth",
SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
);
}
}
}
pub mod reexports {
pub use api_bones;
}
#[cfg(test)]
mod tests {
use crate::error::Error;
#[test]
fn error_display_covers_all_variants() {
assert!(Error::Config("x".into()).to_string().contains("x"));
assert!(Error::Telemetry("x".into()).to_string().contains("x"));
assert!(Error::Database("x".into()).to_string().contains("x"));
assert!(Error::Bind("x".into()).to_string().contains("x"));
assert!(Error::Serve("x".into()).to_string().contains("x"));
}
#[cfg(feature = "telemetry")]
#[test]
fn init_basic_tracing_is_idempotent() {
crate::adapters::observability::telemetry::init_basic_tracing();
crate::adapters::observability::telemetry::init_basic_tracing();
}
}