Updated API 422 Error Response
This commit is contained in:
@@ -11,8 +11,6 @@ use crate::{
|
||||
AppState,
|
||||
};
|
||||
|
||||
const USER_FIELDS: &str = "id, username, email, password_hash, is_admin";
|
||||
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2,
|
||||
|
||||
+20
-22
@@ -5,7 +5,8 @@ use typst::diag::{SourceDiagnostic, Warned};
|
||||
use typst::layout::{Frame, FrameItem};
|
||||
use typst_layout::PagedDocument;
|
||||
use typst_pdf::{pdf, PdfOptions};
|
||||
use typst_render::render;
|
||||
use typst_render::{render, RenderOptions};
|
||||
use typst_svg::SvgOptions;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct DocumentStats {
|
||||
@@ -71,9 +72,14 @@ impl TypstCompiler {
|
||||
warnings: _,
|
||||
} => {
|
||||
let stats = extract_stats(&doc);
|
||||
let svgs = doc.pages().iter().map(typst_svg::svg).collect();
|
||||
let options = SvgOptions::default();
|
||||
let svgs = doc
|
||||
.pages()
|
||||
.iter()
|
||||
.map(|page| typst_svg::svg(page, &options))
|
||||
.collect();
|
||||
let thumbnail = if let Some(page) = doc.pages().first() {
|
||||
typst_svg::svg(page)
|
||||
typst_svg::svg(page, &options)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
@@ -83,15 +89,11 @@ impl TypstCompiler {
|
||||
output: Err(errors),
|
||||
warnings: _,
|
||||
} => {
|
||||
use typst::World;
|
||||
use typst::WorldExt;
|
||||
let diag = errors
|
||||
.into_iter()
|
||||
.map(|d| {
|
||||
let range = d
|
||||
.span
|
||||
.id()
|
||||
.and_then(|id| world.source(id).ok())
|
||||
.and_then(|s| s.range(d.span));
|
||||
let range = world.range(d.span);
|
||||
(d, range)
|
||||
})
|
||||
.collect();
|
||||
@@ -121,15 +123,11 @@ impl TypstCompiler {
|
||||
output: Err(errors),
|
||||
warnings: _,
|
||||
} => {
|
||||
use typst::World;
|
||||
use typst::WorldExt;
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.map(|d| {
|
||||
let range = d
|
||||
.span
|
||||
.id()
|
||||
.and_then(|id| world.source(id).ok())
|
||||
.and_then(|s| s.range(d.span));
|
||||
let range = world.range(d.span);
|
||||
(d, range)
|
||||
})
|
||||
.collect())
|
||||
@@ -149,7 +147,11 @@ impl TypstCompiler {
|
||||
warnings: _,
|
||||
} => {
|
||||
if let Some(page) = doc.pages().first() {
|
||||
let pixmap = render(page, 2.0);
|
||||
let options = RenderOptions {
|
||||
pixel_per_pt: 2.0,
|
||||
..RenderOptions::default()
|
||||
};
|
||||
let pixmap = render(page, &options);
|
||||
if let Ok(encoded) = pixmap.encode_png() {
|
||||
return Ok(encoded);
|
||||
}
|
||||
@@ -160,15 +162,11 @@ impl TypstCompiler {
|
||||
output: Err(errors),
|
||||
warnings: _,
|
||||
} => {
|
||||
use typst::World;
|
||||
use typst::WorldExt;
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.map(|d| {
|
||||
let range = d
|
||||
.span
|
||||
.id()
|
||||
.and_then(|id| world.source(id).ok())
|
||||
.and_then(|s| s.range(d.span));
|
||||
let range = world.range(d.span);
|
||||
(d, range)
|
||||
})
|
||||
.collect())
|
||||
|
||||
@@ -5,7 +5,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Sha256, Digest};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
@@ -25,6 +25,37 @@ pub struct InlineFile {
|
||||
pub data: String, // base64-encoded
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CompileErrorDetail {
|
||||
message: String,
|
||||
severity: String,
|
||||
line: Option<usize>,
|
||||
column: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CompileErrorResponse {
|
||||
error: String,
|
||||
details: Vec<CompileErrorDetail>,
|
||||
}
|
||||
|
||||
fn line_and_column(code: &str, offset: usize) -> (usize, usize) {
|
||||
let mut line = 1;
|
||||
let mut column = 1;
|
||||
for (index, character) in code.char_indices() {
|
||||
if index >= offset {
|
||||
break;
|
||||
}
|
||||
if character == '\n' {
|
||||
line += 1;
|
||||
column = 1;
|
||||
} else {
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
(line, column)
|
||||
}
|
||||
|
||||
fn compute_cache_key(format: &str, code: &str, files: &Option<Vec<InlineFile>>) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(format.as_bytes());
|
||||
@@ -206,6 +237,48 @@ pub async fn render_handler(
|
||||
|
||||
(StatusCode::OK, [(header::CONTENT_TYPE, content_type)], data).into_response()
|
||||
}
|
||||
Err(_) => (StatusCode::UNPROCESSABLE_ENTITY, "Typst compilation failed. Check your code for errors.").into_response(),
|
||||
Err(diagnostics) => {
|
||||
let details: Vec<CompileErrorDetail> = diagnostics
|
||||
.into_iter()
|
||||
.map(|(diagnostic, range)| {
|
||||
let (line, column) = match range.as_ref() {
|
||||
Some(range) => {
|
||||
let (line, column) = line_and_column(&payload.code, range.start);
|
||||
(Some(line), Some(column))
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
CompileErrorDetail {
|
||||
message: diagnostic.message.to_string(),
|
||||
severity: format!("{:?}", diagnostic.severity).to_lowercase(),
|
||||
line,
|
||||
column,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let summary = details
|
||||
.iter()
|
||||
.map(|detail| match (detail.line, detail.column) {
|
||||
(Some(line), Some(column)) => {
|
||||
format!("{} (line {}, column {})", detail.message, line, column)
|
||||
}
|
||||
_ => detail.message.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
|
||||
let error = if summary.is_empty() {
|
||||
"Typst compilation failed.".to_string()
|
||||
} else {
|
||||
format!("Typst compilation failed: {}", summary)
|
||||
};
|
||||
|
||||
(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
Json(CompileErrorResponse { error, details }),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user