Skip to content

Commit 249cd95

Browse files
committed
og_image: Extract OgImageError enum
1 parent a5357a9 commit 249cd95

File tree

5 files changed

+99
-24
lines changed

5 files changed

+99
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/crates_io_og_image/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ minijinja = { version = "=2.10.2", features = ["builtins"] }
1616
reqwest = "=0.12.20"
1717
serde = { version = "=1.0.219", features = ["derive"] }
1818
tempfile = "=3.20.0"
19+
thiserror = "=2.0.12"
1920
tokio = { version = "=1.45.1", features = ["process", "fs"] }
2021

2122
[dev-dependencies]

crates/crates_io_og_image/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ The generated images include:
2424
### Basic Example
2525

2626
```rust
27-
use crates_io_og_image::{OgImageData, OgImageGenerator, OgImageAuthorData};
27+
use crates_io_og_image::{OgImageData, OgImageGenerator, OgImageAuthorData, OgImageError};
2828

2929
#[tokio::main]
30-
async fn main() -> Result<(), Box<dyn std::error::Error>> {
30+
async fn main() -> Result<(), OgImageError> {
3131
// Create a generator instance
3232
let generator = OgImageGenerator::default();
3333

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! Error types for the crates_io_og_image crate.
2+
3+
use std::path::PathBuf;
4+
use thiserror::Error;
5+
6+
/// Errors that can occur when generating OpenGraph images.
7+
#[derive(Debug, Error)]
8+
pub enum OgImageError {
9+
/// Failed to find or execute the Typst binary.
10+
#[error("Failed to find or execute Typst binary: {0}")]
11+
TypstNotFound(#[source] std::io::Error),
12+
13+
/// Environment variable error.
14+
#[error("Environment variable error: {0}")]
15+
EnvVarError(anyhow::Error),
16+
17+
/// Failed to download avatar from URL.
18+
#[error("Failed to download avatar from URL '{url}': {source}")]
19+
AvatarDownloadError {
20+
url: String,
21+
#[source]
22+
source: reqwest::Error,
23+
},
24+
25+
/// Failed to write avatar to file.
26+
#[error("Failed to write avatar to file at {path:?}: {source}")]
27+
AvatarWriteError {
28+
path: PathBuf,
29+
#[source]
30+
source: std::io::Error,
31+
},
32+
33+
/// Template rendering error.
34+
#[error("Template rendering error: {0}")]
35+
TemplateError(#[from] minijinja::Error),
36+
37+
/// Typst compilation failed.
38+
#[error("Typst compilation failed: {stderr}")]
39+
TypstCompilationError {
40+
stderr: String,
41+
stdout: String,
42+
exit_code: Option<i32>,
43+
},
44+
45+
/// I/O error.
46+
#[error("I/O error: {0}")]
47+
IoError(#[from] std::io::Error),
48+
49+
/// Temporary file creation error.
50+
#[error("Failed to create temporary file: {0}")]
51+
TempFileError(std::io::Error),
52+
53+
/// Temporary directory creation error.
54+
#[error("Failed to create temporary directory: {0}")]
55+
TempDirError(std::io::Error),
56+
}

crates/crates_io_og_image/src/lib.rs

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#![doc = include_str!("../README.md")]
22

3+
mod error;
34
mod formatting;
45

6+
pub use error::OgImageError;
7+
58
use crate::formatting::{format_bytes, format_number};
6-
use anyhow::{Context, anyhow};
79
use bytes::Bytes;
810
use crates_io_env_vars::var;
911
use minijinja::{Environment, context};
@@ -121,10 +123,10 @@ impl OgImageGenerator {
121123
/// use crates_io_og_image::OgImageGenerator;
122124
///
123125
/// let generator = OgImageGenerator::from_environment()?;
124-
/// # Ok::<(), anyhow::Error>(())
126+
/// # Ok::<(), crates_io_og_image::OgImageError>(())
125127
/// ```
126-
pub fn from_environment() -> anyhow::Result<Self> {
127-
if let Some(path) = var("TYPST_PATH")? {
128+
pub fn from_environment() -> Result<Self, OgImageError> {
129+
if let Some(path) = var("TYPST_PATH").map_err(OgImageError::EnvVarError)? {
128130
Ok(Self::new(PathBuf::from(path)))
129131
} else {
130132
Ok(Self::default())
@@ -140,7 +142,7 @@ impl OgImageGenerator {
140142
&self,
141143
data: &'a OgImageData<'_>,
142144
assets_dir: &std::path::Path,
143-
) -> anyhow::Result<HashMap<&'a str, String>> {
145+
) -> Result<HashMap<&'a str, String>, OgImageError> {
144146
let mut avatar_map = HashMap::new();
145147

146148
let client = reqwest::Client::new();
@@ -155,18 +157,27 @@ impl OgImageGenerator {
155157
Bytes::from_static(include_bytes!("../assets/test-avatar.png"))
156158
} else {
157159
// Download the avatar from the URL
158-
let response = client.get(*avatar).send().await;
159-
let response = response
160-
.with_context(|| format!("Failed to download avatar from URL: {avatar}"))?;
161-
162-
response.bytes().await.with_context(|| {
163-
format!("Failed to read avatar bytes from URL: {avatar}")
160+
let response = client.get(*avatar).send().await.map_err(|err| {
161+
OgImageError::AvatarDownloadError {
162+
url: (*avatar).to_string(),
163+
source: err,
164+
}
165+
})?;
166+
167+
let bytes = response.bytes().await;
168+
bytes.map_err(|err| OgImageError::AvatarDownloadError {
169+
url: (*avatar).to_string(),
170+
source: err,
164171
})?
165172
};
166173

167174
// Write the bytes to the avatar file
168-
let result = fs::write(&avatar_path, bytes).await;
169-
result.with_context(|| format!("Failed to write avatar to {avatar_path:?}"))?;
175+
fs::write(&avatar_path, bytes).await.map_err(|err| {
176+
OgImageError::AvatarWriteError {
177+
path: avatar_path.clone(),
178+
source: err,
179+
}
180+
})?;
170181

171182
// Store the mapping from the avatar source to the numbered filename
172183
avatar_map.insert(*avatar, filename);
@@ -184,7 +195,7 @@ impl OgImageGenerator {
184195
&self,
185196
data: &OgImageData<'_>,
186197
avatar_map: &HashMap<&str, String>,
187-
) -> anyhow::Result<String> {
198+
) -> Result<String, OgImageError> {
188199
let template = TEMPLATE_ENV.get_template("og-image.typ")?;
189200
let rendered = template.render(context! { data, avatar_map })?;
190201
Ok(rendered)
@@ -199,10 +210,10 @@ impl OgImageGenerator {
199210
/// # Examples
200211
///
201212
/// ```no_run
202-
/// use crates_io_og_image::{OgImageGenerator, OgImageData, OgImageAuthorData};
213+
/// use crates_io_og_image::{OgImageGenerator, OgImageData, OgImageAuthorData, OgImageError};
203214
///
204215
/// # #[tokio::main]
205-
/// # async fn main() -> anyhow::Result<()> {
216+
/// # async fn main() -> Result<(), OgImageError> {
206217
/// let generator = OgImageGenerator::default();
207218
/// let data = OgImageData {
208219
/// name: "my-crate",
@@ -220,9 +231,9 @@ impl OgImageGenerator {
220231
/// # Ok(())
221232
/// # }
222233
/// ```
223-
pub async fn generate(&self, data: OgImageData<'_>) -> anyhow::Result<NamedTempFile> {
234+
pub async fn generate(&self, data: OgImageData<'_>) -> Result<NamedTempFile, OgImageError> {
224235
// Create a temporary folder
225-
let temp_dir = tempfile::tempdir()?;
236+
let temp_dir = tempfile::tempdir().map_err(OgImageError::TempDirError)?;
226237

227238
// Create assets directory and copy logo and icons
228239
let assets_dir = temp_dir.path().join("assets");
@@ -253,7 +264,7 @@ impl OgImageGenerator {
253264
fs::write(&typ_file_path, rendered).await?;
254265

255266
// Create a named temp file for the output PNG
256-
let output_file = NamedTempFile::new()?;
267+
let output_file = NamedTempFile::new().map_err(OgImageError::TempFileError)?;
257268

258269
// Run typst compile command
259270
let output = Command::new(&self.typst_binary_path)
@@ -263,11 +274,17 @@ impl OgImageGenerator {
263274
.arg(&typ_file_path)
264275
.arg(output_file.path())
265276
.output()
266-
.await?;
277+
.await
278+
.map_err(OgImageError::TypstNotFound)?;
267279

268280
if !output.status.success() {
269-
let stderr = String::from_utf8_lossy(&output.stderr);
270-
return Err(anyhow!("typst compile failed: {stderr}"));
281+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
282+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
283+
return Err(OgImageError::TypstCompilationError {
284+
stderr,
285+
stdout,
286+
exit_code: output.status.code(),
287+
});
271288
}
272289

273290
Ok(output_file)

0 commit comments

Comments
 (0)