use crate::error::{AppError, AppResult};
use serde_json::Value;
use std::process::ExitCode;
pub(crate) const SCHEMAS: [(&str, &str); 6] = [
(
"success-envelope",
include_str!("../../docs/schemas/success-envelope.schema.json"),
),
(
"error-envelope",
include_str!("../../docs/schemas/error-envelope.schema.json"),
),
(
"dry-run-envelope",
include_str!("../../docs/schemas/dry-run-envelope.schema.json"),
),
(
"agent-surface",
include_str!("../../docs/schemas/agent-surface.schema.json"),
),
(
"config-envelope",
include_str!("../../docs/schemas/config-envelope.schema.json"),
),
(
"caption-track",
include_str!("../../docs/schemas/caption-track.schema.json"),
),
];
pub async fn print_schema() -> AppResult<ExitCode> {
let mut entries = Vec::with_capacity(SCHEMAS.len());
for (id, text) in SCHEMAS {
let document: Value = serde_json::from_str(text).map_err(AppError::Serde)?;
entries.push(serde_json::json!({ "id": id, "schema": document }));
}
let mut out = serde_json::to_string(&serde_json::json!({ "schemas": entries }))
.map_err(AppError::Serde)?;
out.push('\n');
crate::io::write_subtitle_to_stdout(out.as_bytes()).await?;
Ok(ExitCode::SUCCESS)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_compiled_schema_parses_and_declares_its_title() {
for (id, text) in SCHEMAS {
let document: Value =
serde_json::from_str(text).unwrap_or_else(|e| panic!("{id} must parse: {e}"));
assert_eq!(
document.get("title").and_then(Value::as_str),
Some(id),
"{id} must declare a matching title"
);
assert!(
document.get("$schema").is_some(),
"{id} must declare its meta-schema"
);
}
}
#[test]
fn the_catalogue_covers_every_shipped_schema() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs/schemas");
let mut on_disk: Vec<String> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("{} must be readable: {e}", dir.display()))
.filter_map(|entry| {
let name = entry.ok()?.file_name().to_string_lossy().into_owned();
name.strip_suffix(".schema.json").map(str::to_string)
})
.collect();
on_disk.sort_unstable();
let mut catalogued: Vec<String> = SCHEMAS.iter().map(|(id, _)| (*id).to_string()).collect();
catalogued.sort_unstable();
assert_eq!(
on_disk, catalogued,
"docs/schemas/ and the compiled catalogue must name the same documents"
);
}
#[test]
fn every_schema_id_shares_the_published_base() {
const ID_BASE: &str = "https://docs.rs/youtube-legend-cli/schemas/";
const MINIMUM_SCHEMAS: usize = 6;
let mut checked = 0usize;
for (id, text) in SCHEMAS {
let document: Value =
serde_json::from_str(text).unwrap_or_else(|e| panic!("{id} must parse: {e}"));
let schema_id = document
.get("$id")
.and_then(Value::as_str)
.unwrap_or_else(|| panic!("{id} must declare a string $id"));
assert_eq!(
schema_id,
format!("{ID_BASE}{id}.schema.json"),
"{id} must publish its $id under {ID_BASE} with a matching file name"
);
checked += 1;
}
assert!(
checked >= MINIMUM_SCHEMAS,
"the catalogue must carry at least {MINIMUM_SCHEMAS} schemas, found {checked}"
);
}
#[test]
fn schema_ids_are_unique() {
let mut ids: Vec<&str> = SCHEMAS.iter().map(|(id, _)| *id).collect();
let total = ids.len();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), total);
}
}