miimic-server 0.1.0

HTTP server for miimic
use axum::{
	http::{StatusCode, header},
	response::{IntoResponse, Response},
};
use miimic::{Error, ViewType};

#[derive(Debug, schemars::JsonSchema, serde::Deserialize)]
pub struct RenderQuery {
	pub data: String,
	#[serde(default = "default_width")]
	pub width: u16,
	#[serde(default = "default_expression")]
	pub expression: String,
	#[serde(rename = "texResolution")]
	pub texture_resolution: Option<u16>,
	#[serde(default, rename = "type")]
	pub view_type: FflViewType,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, schemars::JsonSchema, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FflViewType {
	#[default]
	Face,
	FaceOnly,
	AllBody,
}

impl From<FflViewType> for ViewType {
	fn from(value: FflViewType) -> Self {
		match value {
			FflViewType::Face => Self::Avatar,
			FflViewType::FaceOnly => Self::Face,
			FflViewType::AllBody => Self::Body,
		}
	}
}

const fn default_width() -> u16 {
	270
}

fn default_expression() -> String {
	"normal".to_owned()
}

#[derive(Debug, thiserror::Error)]
pub enum AppError {
	#[error(transparent)]
	Miimic(#[from] miimic::Error),
	#[error("render capacity is currently exhausted")]
	RenderCapacity,
	#[error("blocking render task failed: {0}")]
	RenderTask(#[from] tokio::task::JoinError),
	#[error("failed to build HTTP response: {0}")]
	Response(#[from] axum::http::Error),
}

impl aide::OperationOutput for AppError {
	type Inner = Self;
}

impl IntoResponse for AppError {
	fn into_response(self) -> Response {
		match self {
			Self::Miimic(error) => {
				let status = match error {
					Error::InvalidEncoding
					| Error::UnsupportedDataLength(_)
					| Error::InvalidRequest(_) => StatusCode::BAD_REQUEST,
					Error::InvalidMii { .. }
					| Error::InvalidMiiChecksum(_)
					| Error::UnsupportedResource { .. } => StatusCode::UNPROCESSABLE_ENTITY,
					Error::FeatureUnavailable(_) => StatusCode::NOT_IMPLEMENTED,
					_ => StatusCode::INTERNAL_SERVER_ERROR,
				};
				(status, error.to_string()).into_response()
			},
			Self::RenderCapacity => (
				StatusCode::SERVICE_UNAVAILABLE,
				[(header::RETRY_AFTER, "1")],
				"render capacity is currently exhausted",
			)
				.into_response(),
			error @ (Self::RenderTask(_) | Self::Response(_)) => {
				(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()
			},
		}
	}
}