use crate::asset_type::ModelType;
use crate::error::Error;
use crate::manifest::ManifestSource;
use crate::{gguf, onnx, safetensors};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format {
Gguf,
SafeTensors,
Onnx,
}
impl Format {
pub fn detect(data: &[u8]) -> Option<Format> {
if gguf::is_gguf(data) {
Some(Format::Gguf)
} else if safetensors::is_safetensors(data) {
Some(Format::SafeTensors)
} else if onnx::is_onnx(data) {
Some(Format::Onnx)
} else {
None
}
}
pub fn name(self) -> &'static str {
match self {
Format::Gguf => "GGUF",
Format::SafeTensors => "SafeTensors",
Format::Onnx => "ONNX",
}
}
pub fn model_type(self) -> ModelType {
match self {
Format::Gguf | Format::SafeTensors => ModelType::Generic,
Format::Onnx => ModelType::Onnx,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report {
pub format: Format,
pub has_embedded_manifest: bool,
pub has_remote_uri: bool,
}
impl Report {
pub fn is_compliant(&self) -> bool {
self.has_embedded_manifest || self.has_remote_uri
}
}
pub fn embed_manifest(data: &[u8], source: &ManifestSource) -> Result<Vec<u8>, Error> {
embed_manifest_as(data, detect(data)?, source)
}
pub fn embed_manifest_as(
data: &[u8],
format: Format,
source: &ManifestSource,
) -> Result<Vec<u8>, Error> {
match format {
Format::Gguf => gguf::embed(data, source),
Format::SafeTensors => safetensors::embed(data, source),
Format::Onnx => onnx::embed(data, source),
}
}
pub fn read_manifest(data: &[u8]) -> Result<Vec<u8>, Error> {
match detect(data)? {
Format::Gguf => gguf::read_store(data),
Format::SafeTensors => safetensors::read_store(data),
Format::Onnx => onnx::read_store(data),
}
}
pub fn read_manifest_uri(data: &[u8]) -> Result<Option<String>, Error> {
match detect(data)? {
Format::Gguf => gguf::read_uri(data),
Format::SafeTensors => safetensors::read_uri(data),
Format::Onnx => onnx::read_uri(data),
}
}
pub fn remove_manifest(data: &[u8]) -> Result<Vec<u8>, Error> {
match detect(data)? {
Format::Gguf => gguf::remove(data),
Format::SafeTensors => safetensors::remove(data),
Format::Onnx => onnx::remove(data),
}
}
pub fn verify(data: &[u8]) -> Result<Report, Error> {
let format = detect(data)?;
let has_embedded_manifest = match read_manifest(data) {
Ok(_) => true,
Err(Error::NotFound) => false,
Err(e) => return Err(e),
};
let has_remote_uri = read_manifest_uri(data)?.is_some();
Ok(Report {
format,
has_embedded_manifest,
has_remote_uri,
})
}
fn detect(data: &[u8]) -> Result<Format, Error> {
Format::detect(data).ok_or(Error::UnknownFormat)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gguf::tests::sample_gguf;
use crate::onnx::tests::sample_onnx;
use crate::safetensors::tests::sample_safetensors;
#[test]
fn detects_each_format() {
assert_eq!(Format::detect(&sample_gguf()), Some(Format::Gguf));
assert_eq!(
Format::detect(&sample_safetensors(None)),
Some(Format::SafeTensors)
);
assert_eq!(Format::detect(&sample_onnx()), Some(Format::Onnx));
assert_eq!(Format::detect(b"random bytes here!!"), None);
}
#[test]
fn model_types() {
assert_eq!(Format::Onnx.model_type().as_str(), "c2pa.types.model.onnx");
assert_eq!(Format::Gguf.model_type().as_str(), "c2pa.types.model");
}
#[test]
fn dispatch_round_trip_all_formats() {
for data in [sample_gguf(), sample_safetensors(None), sample_onnx()] {
let out = embed_manifest(&data, &ManifestSource::embedded(vec![1, 2, 3])).unwrap();
assert_eq!(read_manifest(&out).unwrap(), vec![1, 2, 3]);
let report = verify(&out).unwrap();
assert!(report.is_compliant());
assert!(report.has_embedded_manifest);
let cleaned = remove_manifest(&out).unwrap();
assert!(matches!(read_manifest(&cleaned), Err(Error::NotFound)));
}
}
#[test]
fn unknown_format_errors() {
assert!(matches!(
embed_manifest(b"nope", &ManifestSource::embedded(vec![1])),
Err(Error::UnknownFormat)
));
}
}