use crate::core::{MediaType, TransformError, TransformRequest, TransformResult};
pub mod raster;
#[cfg(feature = "svg")]
pub mod svg;
#[must_use = "this function returns the transform result without side effects"]
pub fn transform(request: TransformRequest) -> Result<TransformResult, TransformError> {
if request.input.media_type == MediaType::Svg {
#[cfg(feature = "svg")]
{
return svg::transform_svg(request);
}
#[cfg(not(feature = "svg"))]
{
let _ = request;
return Err(TransformError::CapabilityMissing(
"SVG processing is not enabled in this build".to_string(),
));
}
}
if request.options.format == Some(MediaType::Svg) {
return Err(TransformError::UnsupportedOutputMediaType(MediaType::Svg));
}
if request.options.format == Some(MediaType::Gif) {
return Err(TransformError::UnsupportedOutputMediaType(MediaType::Gif));
}
if request.input.media_type == MediaType::Gif && request.input.metadata.frame_count > 1 {
return Err(TransformError::UnsupportedInputMediaType(format!(
"animated GIF is not supported ({} frames); truss transforms single-frame images only",
request.input.metadata.frame_count
)));
}
raster::transform_raster(request)
}
#[cfg(test)]
mod tests {
use super::transform;
use crate::core::{
Artifact, ArtifactMetadata, MediaType, TransformError, TransformOptions, TransformRequest,
};
fn gif_artifact(frame_count: u32) -> Artifact {
Artifact::new(
b"GIF89a".to_vec(),
MediaType::Gif,
ArtifactMetadata {
width: Some(4),
height: Some(4),
frame_count,
duration: None,
has_alpha: Some(false),
},
)
}
#[test]
fn transform_rejects_an_animated_gif_input() {
let error = transform(TransformRequest::new(
gif_artifact(12),
TransformOptions {
format: Some(MediaType::Png),
..TransformOptions::default()
},
))
.expect_err("an animated gif should be refused, not reduced to one frame");
match error {
TransformError::UnsupportedInputMediaType(message) => {
assert!(
message.contains("animated GIF") && message.contains("12 frames"),
"the error should name the format and the frame count, got: {message}"
);
}
other => panic!("expected UnsupportedInputMediaType, got: {other}"),
}
}
#[test]
fn transform_rejects_gif_output() {
let error = transform(TransformRequest::new(
Artifact::new(
b"\x89PNG\r\n\x1a\n".to_vec(),
MediaType::Png,
ArtifactMetadata::default(),
),
TransformOptions {
format: Some(MediaType::Gif),
..TransformOptions::default()
},
))
.expect_err("gif output should be refused");
assert!(
matches!(
error,
TransformError::UnsupportedOutputMediaType(MediaType::Gif)
),
"expected UnsupportedOutputMediaType(Gif), got: {error}"
);
}
#[test]
fn transform_accepts_a_single_frame_gif_past_the_animation_guard() {
let error = transform(TransformRequest::new(
gif_artifact(1),
TransformOptions {
format: Some(MediaType::Png),
..TransformOptions::default()
},
))
.expect_err("truncated gif bytes cannot decode");
assert!(
matches!(error, TransformError::DecodeFailed(_)),
"a single-frame gif should reach the decoder, got: {error}"
);
}
}