The code was hand-formatted -- close to rustfmt's output but not it, mostly in keeping chains and call arguments on one line where the formatter would break them. That is a per-line decision every future change has to make again, and reproducing it would mean a config whose only job is to preserve how the code already looks. So this is `cargo fmt` at its defaults, with no rustfmt.toml, which is where the sibling dev-updater checkout already sits: it is clean at the defaults today, so the two repos now agree on layout without either of them configuring it. Formatting only -- no behaviour, no renames, nothing reordered. Verified after: cargo test (35 pass), cargo clippy --all-targets clean, cargo fmt --check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
63 lines
2.1 KiB
Rust
63 lines
2.1 KiB
Rust
//! The image types that travel between the phone, the session
|
|
//! directories, and a driver's dialect.
|
|
//!
|
|
//! Media type and file extension have to agree in four places -- storing
|
|
//! an upload, serving it back, handing it to a CLI as a content block, and
|
|
//! saving one a tool produced -- so the table lives here once. The
|
|
//! *default* for an unrecognized type is deliberately not here: it differs
|
|
//! by direction (a phone upload is a photo, a produced image is a
|
|
//! screenshot), so each caller states its own.
|
|
|
|
/// Media type to extension. Only the types Claude's API accepts as image
|
|
/// content blocks -- anything else has nowhere to go.
|
|
const IMAGE_TYPES: [(&str, &str); 4] = [
|
|
("image/png", "png"),
|
|
("image/jpeg", "jpg"),
|
|
("image/gif", "gif"),
|
|
("image/webp", "webp"),
|
|
];
|
|
|
|
/// The extension to store `media_type` under, or `None` if it isn't an
|
|
/// image type this server handles.
|
|
pub fn extension_for(media_type: &str) -> Option<&'static str> {
|
|
IMAGE_TYPES
|
|
.iter()
|
|
.find(|(known, _)| *known == media_type)
|
|
.map(|(_, extension)| *extension)
|
|
}
|
|
|
|
/// The media type of a stored file, from its extension. Names are
|
|
/// server-generated (`<hex>.<extension>`, always lowercase), so no case
|
|
/// folding is needed; `None` for anything else.
|
|
pub fn media_type_for(name: &str) -> Option<&'static str> {
|
|
let (_, extension) = name.rsplit_once('.')?;
|
|
IMAGE_TYPES
|
|
.iter()
|
|
.find(|(_, known)| *known == extension)
|
|
.map(|(media_type, _)| *media_type)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn the_two_directions_agree() {
|
|
for (media_type, extension) in IMAGE_TYPES {
|
|
assert_eq!(extension_for(media_type), Some(extension));
|
|
assert_eq!(
|
|
media_type_for(&format!("abc123.{extension}")),
|
|
Some(media_type)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_types_are_the_callers_problem() {
|
|
assert_eq!(extension_for("application/pdf"), None);
|
|
assert_eq!(media_type_for("abc123.pdf"), None);
|
|
// No extension at all -- not "the whole name is the extension".
|
|
assert_eq!(media_type_for("abc123"), None);
|
|
}
|
|
}
|