use std::sync::OnceLock;
use utoipa::{OpenApi, ToSchema};
pub(crate) const SWAGGER_HTML: &str = r##"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoLite API — Swagger UI</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
<style>
/* cap the readable content width (swagger-ui defaults to 1460px) */
.swagger-ui .wrapper {
max-width: 1024px;
}
/* no endpoint groups: hide the tag section header so endpoints render directly */
.swagger-ui .opblock-tag {
display: none;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin></script>
<script>
window.onload = function () {
window.ui = SwaggerUIBundle({
url: "/openapi.json",
dom_id: "#swagger-ui",
defaultModelsExpandDepth: -1,
});
};
</script>
</body>
</html>
"##;
#[derive(ToSchema)]
#[allow(dead_code)]
pub(crate) struct ApiError {
pub(crate) error: String,
}
#[utoipa::path(
get,
path = "/geocode",
params(
("query" = String, Query, description = "free-text address, or `lat,lon` coordinates to reverse geocode"),
("friendly_name_format" = Option<String>, Query, description = "template for `friendly_name`, e.g. `{admin_level_8_name}` and/or `{house_number}`"),
("quality" = Option<f64>, Query, minimum = 0.0, maximum = 1.0, description = "minimum match quality in `[0, 1]`; lower-quality matches are dropped, a value outside the range is a 400"),
("bounding_wkt" = Option<String>, Query, description = "WKT POLYGON or MULTIPOLYGON; keeps only matches inside it"),
("last_admin_levels" = Option<String>, Query, description = "comma-separated admin levels (e.g. `8,10`); keeps matches whose last admin level is one of them (OR)"),
("include_wkt" = Option<bool>, Query, description = "include admin-level `wkt` geometry in the response (default `true`; set `false` to omit)"),
),
responses(
(status = 200, description = "geocoding result", body = crate::address::query_output),
(status = 400, description = "missing or invalid query parameter", body = ApiError),
(status = 503, description = "text_to_address service unavailable (index not loaded)", body = ApiError),
(status = 500, description = "response serialization error", body = ApiError),
),
)]
#[allow(dead_code)]
fn geocode() {}
#[utoipa::path(
get,
path = "/status",
responses(
(status = 200, description = "all services available", body = crate::interfaces::http::status::status_output),
(status = 503, description = "one or more services unavailable", body = crate::interfaces::http::status::status_output),
),
)]
#[allow(dead_code)]
fn status() {}
#[derive(OpenApi)]
#[openapi(
info(
title = "geolite",
description = "",
),
paths(geocode, status),
components(schemas(
crate::address::query_output,
crate::address::query_service,
crate::address::query_match,
crate::address::admin_level,
crate::address::query_match_attributes,
crate::address::query_house_number,
crate::address::house_number_match,
crate::interfaces::http::status::status_output,
crate::interfaces::http::status::database_status,
ApiError,
)),
)]
struct ApiDoc;
pub(crate) fn spec_json() -> &'static str {
static SPEC: OnceLock<String> = OnceLock::new();
SPEC.get_or_init(|| {
let mut doc = ApiDoc::openapi();
doc.info.contact = None;
doc.to_json().expect("openapi spec serialization")
})
}