#![deny(missing_docs)]
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ValidationReport {
pub uri: Option<String>,
pub mime_type: Option<MimeType>,
pub validator_version: String,
pub validated_at: Option<String>,
pub issues: Issues,
pub info: Option<Info>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum MimeType {
#[serde(rename = "model/gltf+json")]
ModelGltfJson,
#[serde(rename = "model/gltf-binary")]
ModelGltfBinary,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Issues {
pub num_errors: u32,
pub num_warnings: u32,
pub num_infos: u32,
pub num_hints: u32,
pub messages: Vec<Message>,
pub truncated: bool,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Message {
pub code: String,
pub severity: Severity,
pub pointer: Option<String>,
pub offset: Option<u32>,
pub message: String,
}
#[derive(Debug, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
#[serde(rename_all = "camelCase")]
#[repr(u8)]
pub enum Severity {
Error = 0,
Warning = 1,
Information = 2,
Hint = 3,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Info {
pub version: String,
pub min_version: Option<String>,
pub generator: Option<String>,
pub extensions_used: Option<Vec<String>>,
pub extensions_required: Option<Vec<String>>,
pub resources: Option<Vec<Resource>>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Resource {
pub pointer: String,
pub storage: Storage,
pub mime_type: Option<String>,
pub byte_length: Option<u32>,
pub uri: Option<String>,
pub image: Option<Image>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Storage {
#[serde(rename = "data-uri")]
DataUri,
#[serde(rename = "buffer-view")]
BufferView,
#[serde(rename = "glb")]
Glb,
#[serde(rename = "external")]
External,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Image {
pub width: u32,
pub height: u32,
pub format: Option<Format>,
pub primaries: Option<Primaries>,
pub transfer: Option<Transfer>,
pub bits: Option<u32>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Format {
Rgb,
Rgba,
Luminance,
LuminanceAlpha,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Primaries {
Srgb,
Custom,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Transfer {
Linear,
Srgb,
Custom,
}
const BINARY_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/gltf_validator"));
pub struct GltfValidator {
installed_path: tempfile::TempPath,
}
fn init() -> Result<tempfile::TempPath> {
use std::io::Write;
let mut file = tempfile::NamedTempFile::new()?;
file.write_all(BINARY_BYTES)?;
let (underlying_file, installed_path) = file.into_parts();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o755);
underlying_file.set_permissions(perms)?;
}
underlying_file.sync_all()?;
Ok(installed_path)
}
impl GltfValidator {
pub fn new() -> Result<Self> {
let installed_path = init()?;
Ok(Self { installed_path })
}
fn run_inner(&self, path: &std::path::Path) -> Result<ValidationReport> {
if !path.exists() {
return Err(anyhow::anyhow!("File does not exist: {}", path.display()));
}
let output = std::process::Command::new(&self.installed_path)
.arg("-o")
.arg(path)
.output()?;
let json_string = String::from_utf8_lossy(&output.stdout);
let report: ValidationReport = serde_json::from_str(&json_string)?;
Ok(report)
}
pub fn run<P>(&self, path: P) -> Result<ValidationReport>
where
P: AsRef<std::path::Path>,
{
self.run_inner(path.as_ref())
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_cube() -> Result<(), Box<dyn std::error::Error>> {
let path = "tests/cube.glb";
let validator = super::GltfValidator::new()?;
let report = validator.run(path)?;
assert_eq!(report.issues.num_errors, 0);
assert_eq!(report.issues.num_warnings, 0);
Ok(())
}
}