#![feature(proc_macro_hygiene, decl_macro, str_split_once)]
use ::clingo::ClingoError;
use rocket::{
get,
http::Status,
request::FromQuery,
response::{Responder, Response},
routes,
};
use rocket_contrib::{json::Json, serve::StaticFiles};
use serde::Serialize;
use thiserror::Error;
use std::{
fs::File,
io::{Cursor, Error as IoError},
num::ParseIntError,
};
mod clingo;
use crate::clingo::ZeroBased;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SolveRequest {
id: u64,
size: u32,
fixed_queens: Vec<(ZeroBased<u32>, ZeroBased<u32>)>,
}
#[derive(Error, Debug)]
pub enum SolveRequestError {
#[error("Request is missing id: u64")]
MissingId,
#[error("Request is missing size: i32")]
MissingSize,
#[error("More than one ID specified")]
MoreThanOneId,
#[error("More than one size specified")]
MoreThanOneSize,
#[error("Failed to parse the ID as u64: {_0}")]
FailedToParseId(#[source] ParseIntError),
#[error("Failed to parse the size as i32: {_0}")]
FailedToParseSize(#[source] ParseIntError),
#[error("No colon between queen coordinates")]
MissingColonInQueenCoordinates,
#[error("Failed to parse queen coordinates: {_0}")]
FailedToParseQueenCoordinates(#[source] ParseIntError),
}
#[derive(Debug, Clone, Serialize)]
pub struct SolveResponse {
id: u64,
queens: Vec<(ZeroBased<u32>, ZeroBased<u32>)>,
}
#[derive(Debug, Error)]
pub enum SolveError {
#[error("No solution exists")]
NoSolution,
#[error("Clingo error: {_0}")]
ClingoError(#[from] ClingoError),
}
impl<'r> Responder<'r> for SolveError {
fn respond_to(self, _request: &rocket::Request) -> rocket::response::Result<'r> {
let mut response = Response::new();
match self {
SolveError::NoSolution => {
let status = Status::new(598, "No solution found");
response.set_status(status);
response.set_sized_body(Cursor::new("No solution found"));
}
SolveError::ClingoError(_why) => {
let status = Status::new(597, "Internal clingo error");
response.set_status(status);
response.set_sized_body(Cursor::new("Internal clingo error"));
}
};
Ok(response)
}
}
impl<'q> FromQuery<'q> for SolveRequest {
type Error = SolveRequestError;
fn from_query(query: rocket::request::Query<'q>) -> Result<Self, Self::Error> {
let mut id = Err(SolveRequestError::MissingId);
let mut size = Err(SolveRequestError::MissingSize);
let mut fixed_queens = vec![];
for (key, value) in query.map(|item| item.key_value_decoded()) {
match key.as_str() {
"id" => {
if id.is_ok() {
id = Err(SolveRequestError::MoreThanOneId)
} else {
id = str::parse(&value)
.map_err(|why| SolveRequestError::FailedToParseId(why))
}
}
"size" => {
if size.is_ok() {
size = Err(SolveRequestError::MoreThanOneSize)
} else {
size = str::parse(&value)
.map_err(|why| SolveRequestError::FailedToParseSize(why))
}
}
"queen" => {
if let Some((x_coord, y_coord)) = value.split_once(':') {
let x_coord = x_coord
.parse()
.map(ZeroBased::new)
.map_err(SolveRequestError::FailedToParseQueenCoordinates)?;
let y_coord = y_coord
.parse()
.map(ZeroBased::new)
.map_err(SolveRequestError::FailedToParseQueenCoordinates)?;
fixed_queens.push((x_coord, y_coord));
} else {
return Err(SolveRequestError::MissingColonInQueenCoordinates);
}
}
_ => {}
}
}
Ok(SolveRequest {
id: id?,
size: size?,
fixed_queens,
})
}
}
#[get("/")]
fn index() -> Result<File, IoError> {
File::open("../frontend/Queens.html")
}
#[get("/favicon.ico")]
fn favicon() -> Result<File, IoError> {
File::open("../frontend/static/favicon-32x32.png")
}
#[get("/solve?<req..>")]
fn solve(req: SolveRequest) -> Result<Json<SolveResponse>, SolveError> {
let value = clingo::solve(req)?;
Ok(Json(value))
}
fn main() {
rocket::ignite()
.mount("/", routes![index, solve, favicon])
.mount("/static", StaticFiles::from("../frontend/static"))
.launch();
}