use athene::prelude::*;
use utoipa::ToSchema;
use utoipa::{
openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
Modify, OpenApi,
};
use utoipa_swagger_ui::Config;
use std::sync::Arc;
const API_KEY_NAME: &str = "todo_apikey";
const API_KEY: &str = "utoipa-rocks";
#[derive(Serialize, Deserialize, ToSchema, Clone)]
struct Todo {
pub id: u64,
#[schema(example = "Buy groceries")]
pub text: String,
pub completed: bool,
}
#[derive(Serialize, Deserialize, ToSchema)]
enum TodoError {
#[schema(example = "Todo already exists")]
Conflict(String),
#[schema(example = "id = 1")]
NotFound(String),
#[schema(example = "missing api key")]
Unauthorized(String),
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct Pagination {
pub offset: Option<usize>,
pub limit: Option<usize>,
}
#[utoipa::path(
get,
path = "/todos",
responses(
(status = 200, description = "List all todos successfully", body = [Todo])
)
)]
async fn list(_req: Request) -> impl Responder {
}
#[utoipa::path(
post,
path = "/todos",
request_body = Todo,
responses(
(status = 201, description = "Todo item created successfully", body = Todo),
(status = 409, description = "Todo already exists", body = TodoError)
)
)]
async fn create(_req: Request) -> impl Responder {}
#[utoipa::path(
post,
path = "/todos/{id}",
responses(
(status = 200, description = "Todo item found successfully", body = Todo),
(status = 404, description = "Todo not found")
),
params(
("id" = u64, Path, description = "Todo database id")
),
security(
(), // <-- make optional authentication
("api_key" = [])
)
)]
async fn show(_req: Request) -> impl Responder {}
#[utoipa::path(
put,
path = "/todos/{id}",
responses(
(status = 200, description = "Todo marked done successfully"),
(status = 404, description = "Todo not found")
),
params(
("id" = u64, Path, description = "Todo database id")
),
security(
(), // <-- make optional authentication
("api_key" = [])
)
)]
async fn update(_req: Request) -> impl Responder {}
#[utoipa::path(
delete,
path = "/todos/{id}",
responses(
(status = 200, description = "Todo marked done successfully"),
(status = 401, description = "Unauthorized to delete Todo", body = TodoError, example = json!(TodoError::Unauthorized(String::from("missing api key")))),
(status = 404, description = "Todo not found", body = TodoError, example = json!(TodoError::NotFound(String::from("id = 1"))))
),
params(
("id" = u64, Path, description = "Todo database id")
),
security(
("api_key" = [])
)
)]
async fn delete(req: Request) -> impl Responder {
let res = check_api_key(true, req);
res
}
fn check_api_key(require_api_key: bool, req: Request) -> impl Responder {
let res = Builder::new();
match req.headers().get(API_KEY_NAME) {
Some(header) if header != API_KEY => {
res.status(StatusCode::UNAUTHORIZED).json(&TodoError::Unauthorized(String::from("incorrect api key")))
}
None if require_api_key => {
res.status(StatusCode::UNAUTHORIZED).json(&TodoError::Unauthorized(String::from("missing api key")))
}
_ => res
}
}
struct SecurityAddon;
impl Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
if let Some(components) = openapi.components.as_mut() {
components.add_security_scheme(
"api_key",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new(API_KEY_NAME))),
);
}
}
}
#[derive(OpenApi)]
#[openapi(
paths(
list,
create,
update,
delete,
),
components(
schemas(Todo, TodoError)
),
modifiers(&SecurityAddon),
tags(
(name = "todo", description = "Todo items management API")
)
)]
struct ApiDoc;
pub async fn openapi_json(_req: Request) -> impl Responder {
let apidoc = Arc::new(ApiDoc::openapi());
let res = Builder::new();
res.json(&*apidoc)
}
pub async fn serve_swagger(req: Request) -> impl Responder {
let config = Arc::new(Config::from("/api-doc/openapi.json"));
let path = req.uri().path().to_string();
let tail = path.strip_prefix("/swagger-ui/").unwrap();
let res = Builder::new();
match utoipa_swagger_ui::serve(tail, config) {
Ok(swagger_file) => swagger_file
.map(|file| {
res.with(&file.content_type, file.bytes.to_vec())
})
.unwrap(),
Err(error) => res.status(500).text(error.to_string()),
}
}
pub fn openapi_router(r: Router) -> Router {
let r = r.get(
"/api-doc/openapi.json",
openapi_json,
).get("/swagger-ui/**", serve_swagger);
let r = r.get("/todos", list)
.post("/todos", create)
.get("/todos/:id", show)
.put("/todos/:id", update)
.delete("/todos/:id", delete);
r
}
#[tokio::main]
pub async fn main() -> Result<()> {
let app = athene::new().router(openapi_router);
app.listen("127.0.0.1:7878").await
}