pub mod de;
pub mod dependencies;
pub mod lookup;
pub mod node;
pub mod project;
pub mod report;
pub mod schedule;
pub mod sources;
pub fn schema<T: schemars::JsonSchema>(version: &str) -> serde_json::Value {
let mut out = generated::<T>(schemars::generate::Contract::Deserialize);
without_null(&mut out);
if let serde_json::Value::Object(m) = &mut out {
m.insert("x-dre-schema-version".into(), version.into());
if m.get("type").and_then(|t| t.as_str()) == Some("object") {
m.insert(
"patternProperties".into(),
serde_json::json!({"^x-": {"description": "Anything, for YAML anchors (`&name`) reused with aliases (`*name`) and merges (`<<: *name`). DRE ignores these keys."}}),
);
}
}
in_order(&mut out);
out
}
pub fn artifact_schema<T: schemars::JsonSchema>(id: &str) -> serde_json::Value {
let mut out = generated::<T>(schemars::generate::Contract::Serialize);
if let serde_json::Value::Object(m) = &mut out {
m.insert("$id".into(), id.into());
}
in_order(&mut out);
out
}
fn generated<T: schemars::JsonSchema>(contract: schemars::generate::Contract) -> serde_json::Value {
let mut settings = schemars::generate::SchemaSettings::draft2020_12();
settings.contract = contract;
let mut schema = settings.into_generator().into_root_schema_for::<T>().to_value();
if let serde_json::Value::Object(m) = &mut schema {
m.insert(
"$schema".into(),
"https://json-schema.org/draft/2020-12/schema".into(),
);
}
schema
}
fn in_order(v: &mut serde_json::Value) {
const ORDER: [&str; 14] = [
"$schema",
"x-dre-schema-version",
"$id",
"title",
"type",
"description",
"deprecated",
"const",
"x-doc-type",
"properties",
"required",
"additionalProperties",
"minProperties",
"$defs",
];
use serde_json::Value;
match v {
Value::Object(m) => {
m.values_mut().for_each(in_order);
if m.contains_key("$schema")
|| m.contains_key("type")
|| m.contains_key("$ref")
|| m.contains_key("oneOf")
|| m.contains_key("const")
{
let rank = |k: &str| ORDER.iter().position(|o| *o == k).unwrap_or(ORDER.len() - 1);
let mut entries: Vec<(String, Value)> = std::mem::take(m).into_iter().collect();
entries.sort_by_key(|(k, _)| rank(k));
m.extend(entries);
}
}
Value::Array(a) => a.iter_mut().for_each(in_order),
_ => {}
}
}
fn without_null(v: &mut serde_json::Value) {
use serde_json::Value;
match v {
Value::Object(m) => {
let keep_null = m.remove("x-dre-null").is_some();
if m.get("default") == Some(&Value::Null) {
m.remove("default");
}
if let Some(Value::Array(types)) = m.get_mut("type")
&& !keep_null
{
types.retain(|t| t != "null");
if types.len() == 1 {
let only = types.remove(0);
m.insert("type".into(), only);
}
}
if let Some(Value::Array(values)) = m.get_mut("enum") {
values.retain(|v| !v.is_null());
}
if m.get("format")
.and_then(Value::as_str)
.is_some_and(|f| f.starts_with("int") || f.starts_with("uint"))
{
m.remove("format");
}
if let Some(Value::Array(any)) = m.get("anyOf") {
let rest: Vec<Value> = any
.iter()
.filter(|s| s.get("type") != Some(&Value::from("null")))
.cloned()
.collect();
if rest.len() == 1 && rest.len() < any.len() {
m.remove("anyOf");
if let Value::Object(inner) = rest.into_iter().next().unwrap() {
for (k, v) in inner {
m.entry(k).or_insert(v);
}
}
}
}
m.values_mut().for_each(without_null);
}
Value::Array(a) => a.iter_mut().for_each(without_null),
_ => {}
}
}
pub fn artifact_schemas() -> Vec<(&'static str, serde_json::Value)> {
vec![
(
"run-results.schema.json",
artifact_schema::<crate::run_results::RunResults>(
"https://github.com/get-dre/dre/blob/master/docs/run-results.schema.json",
),
),
(
"manifest.schema.json",
artifact_schema::<crate::manifest::Manifest>(
"https://github.com/get-dre/dre/blob/master/docs/manifest.schema.json",
),
),
]
}
pub fn schemas(version: &str) -> Vec<(&'static str, serde_json::Value)> {
vec![
("project", schema::<project::ProjectFile>(version)),
("report", schema::<report::ReportFile>(version)),
("sets", schema::<schedule::SetsFile>(version)),
("schedules", schema::<schedule::SchedulesFile>(version)),
("timings", schema::<schedule::TimingsFile>(version)),
("profiles", schema::<crate::profiles::ProfilesFile>(version)),
("dependencies", schema::<dependencies::DependenciesFile>(version)),
("lookup", schema::<lookup::LookupFile>(version)),
("sources", schema::<sources::SourcesFile>(version)),
]
}
pub fn to_yaml<T: serde::Serialize>(value: &T) -> Result<String, String> {
let mut options = serde_saphyr::SerializerOptions::default();
options.prefer_block_scalars = false;
serde_saphyr::to_string_with_options(value, options).map_err(|e| e.to_string())
}