use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
fn required_str<'a>(value: &'a Value, key: &str) -> &'a str {
value[key]
.as_str()
.unwrap_or_else(|| panic!("schema field `{key}` must be a string"))
}
fn rust_string(value: &str) -> String {
format!("{value:?}")
}
fn generate_embedded_schema(schema_path: &Path, output_path: &Path) {
let mut generated = String::from(
"// Generated by build.rs. Static metadata stays in flash on no_std targets.\n",
);
if !schema_path.is_file() {
generated.push_str("const EMBEDDED_SCHEMA_ENDPOINTS: &[EndpointDefinition] = &[];\n");
generated.push_str("const EMBEDDED_SCHEMA_TYPES: &[DataTypeDefinition] = &[];\n");
fs::write(output_path, generated).expect("write empty embedded schema");
return;
}
let bytes = fs::read(schema_path).expect("read telemetry_config.json");
let schema: Value = serde_json::from_slice(&bytes).expect("parse telemetry_config.json");
let endpoints = schema["endpoints"]
.as_array()
.expect("schema endpoints must be an array");
let types = schema["types"]
.as_array()
.expect("schema types must be an array");
let mut endpoint_ids = HashMap::new();
generated.push_str("const EMBEDDED_SCHEMA_ENDPOINTS: &[EndpointDefinition] = &[\n");
for (index, endpoint) in endpoints.iter().enumerate() {
let id = 100_u32 + u32::try_from(index).expect("too many schema endpoints");
let name = required_str(endpoint, "name");
let rust_name = endpoint["rust"].as_str().unwrap_or(name);
let description = endpoint["doc"]
.as_str()
.or_else(|| endpoint["description"].as_str())
.unwrap_or("");
let link_local = endpoint["link_local_only"].as_bool().unwrap_or(false)
|| endpoint["broadcast_mode"].as_str() == Some("Never");
endpoint_ids.insert(rust_name.to_owned(), id);
writeln!(
generated,
" EndpointDefinition {{ id: DataEndpoint({id}), name: {}, description: {}, link_local_only: {link_local} }},",
rust_string(name),
rust_string(description),
)
.unwrap();
}
generated.push_str("];\n");
generated.push_str("const EMBEDDED_SCHEMA_TYPES: &[DataTypeDefinition] = &[\n");
for (index, ty) in types.iter().enumerate() {
let id = 100_u32 + u32::try_from(index).expect("too many schema types");
let name = required_str(ty, "name");
let description = ty["doc"]
.as_str()
.or_else(|| ty["description"].as_str())
.unwrap_or("");
let class = match required_str(ty, "class") {
"Data" => "MessageClass::Data",
"Error" => "MessageClass::Error",
"Warning" => "MessageClass::Warning",
other => panic!("unsupported message class `{other}`"),
};
let data_type = match required_str(&ty["element"], "data_type") {
"Float64" => "MessageDataType::Float64",
"Float32" => "MessageDataType::Float32",
"UInt8" => "MessageDataType::UInt8",
"UInt16" => "MessageDataType::UInt16",
"UInt32" => "MessageDataType::UInt32",
"UInt64" => "MessageDataType::UInt64",
"UInt128" => "MessageDataType::UInt128",
"Int8" => "MessageDataType::Int8",
"Int16" => "MessageDataType::Int16",
"Int32" => "MessageDataType::Int32",
"Int64" => "MessageDataType::Int64",
"Int128" => "MessageDataType::Int128",
"Bool" => "MessageDataType::Bool",
"String" => "MessageDataType::String",
"Binary" => "MessageDataType::Binary",
"NoData" => "MessageDataType::NoData",
other => panic!("unsupported message data type `{other}`"),
};
let element = match required_str(&ty["element"], "kind") {
"Static" => format!(
"MessageElement::Static({}, {data_type}, {class})",
ty["element"]["count"].as_u64().unwrap_or(1)
),
"Dynamic" => format!("MessageElement::Dynamic({data_type}, {class})"),
other => panic!("unsupported message element kind `{other}`"),
};
let reliable = match ty["reliable_mode"].as_str() {
Some("Ordered") => "ReliableMode::Ordered",
Some("Unordered") => "ReliableMode::Unordered",
Some("None") | None if ty["reliable"].as_bool().unwrap_or(false) => {
"ReliableMode::Ordered"
}
Some("None") | None => "ReliableMode::None",
Some(other) => panic!("unsupported reliable mode `{other}`"),
};
let e2e = match ty["e2e_encryption"].as_str().unwrap_or("PreferOff") {
"PreferOff" | "prefer_off" | "off" | "false" => "E2eEncryptionPolicy::PreferOff",
"PreferOn" | "prefer_on" | "preferred" | "true" => "E2eEncryptionPolicy::PreferOn",
"RequireOn" | "require_on" | "required" => "E2eEncryptionPolicy::RequireOn",
other => panic!("unsupported e2e encryption policy `{other}`"),
};
let endpoint_names = ty["endpoints"]
.as_array()
.expect("type endpoints must be an array");
let endpoint_expr = endpoint_names
.iter()
.map(|endpoint| {
let name = endpoint.as_str().expect("type endpoint must be a string");
let id = endpoint_ids
.get(name)
.unwrap_or_else(|| panic!("unknown endpoint `{name}`"));
format!("DataEndpoint({id})")
})
.collect::<Vec<_>>()
.join(", ");
let priority = ty["priority"].as_u64().unwrap_or(0);
writeln!(
generated,
" DataTypeDefinition {{ id: DataType({id}), name: {}, description: {}, element: {element}, endpoints: &[{endpoint_expr}], reliable: {reliable}, priority: {priority}, e2e_encryption: {e2e} }},",
rust_string(name),
rust_string(description),
)
.unwrap();
}
generated.push_str("];\n");
fs::write(output_path, generated).expect("write generated embedded schema");
}
fn main() {
println!("cargo:rustc-check-cfg=cfg(sedsnet_has_telemetry_config_json)");
println!("cargo:rerun-if-changed=telemetry_config.json");
let schema_path = Path::new("telemetry_config.json");
if schema_path.is_file() {
println!("cargo:rustc-cfg=sedsnet_has_telemetry_config_json");
}
let output_path =
PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR")).join("embedded_schema.rs");
generate_embedded_schema(schema_path, &output_path);
for key in [
"DEVICE_IDENTIFIER",
"MAX_RECENT_RX_IDS",
"STARTING_QUEUE_SIZE",
"MAX_QUEUE_BUDGET",
"MAX_QUEUE_SIZE",
"QUEUE_GROW_STEP",
"PAYLOAD_COMPRESS_THRESHOLD",
"STATIC_STRING_LENGTH",
"STATIC_HEX_LENGTH",
"STRING_PRECISION",
"MAX_STACK_PAYLOAD",
"MAX_HANDLER_RETRIES",
"RELIABLE_RETRANSMIT_MS",
"RELIABLE_MAX_RETRIES",
"RELIABLE_MAX_PENDING",
"RELIABLE_MAX_RETURN_ROUTES",
"RELIABLE_MAX_END_TO_END_PENDING",
"RELIABLE_MAX_END_TO_END_ACK_CACHE",
"SEDSNET_STATIC_SCHEMA_PATH",
"SEDSNET_STATIC_IPC_SCHEMA_PATH",
] {
println!("cargo:rerun-if-env-changed={key}");
}
}