mod builtin;
mod render;
mod validate;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
pub use builtin::{BUILTIN_NAMES, load_embedded};
pub const SCHEMA_VERSION_MIN: u32 = 1;
pub const SCHEMA_VERSION_MAX: u32 = 1;
pub const RESERVED_VARS: &[&str] = &[
"DID",
"SIGNING_KEY_MB",
"KA_KEY_MB",
"VTA_DID",
"VTA_URL",
"CONTEXT_ID",
"CONTEXT_DID",
"NOW",
];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Scope {
Builtin,
Global,
Context {
#[serde(rename = "contextId")]
context_id: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidTemplate {
#[serde(rename = "schemaVersion")]
pub schema_version: u32,
pub name: String,
pub kind: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub methods: Vec<String>,
#[serde(default, rename = "requiredVars")]
pub required_vars: Vec<String>,
#[serde(default, rename = "optionalVars")]
pub optional_vars: serde_json::Map<String, Value>,
#[serde(default)]
pub defaults: serde_json::Map<String, Value>,
pub document: Value,
}
impl DidTemplate {
pub fn from_json(value: Value) -> Result<Self, TemplateError> {
let tpl: DidTemplate = serde_json::from_value(value)?;
tpl.validate()?;
Ok(tpl)
}
pub fn load_file(path: impl AsRef<Path>) -> Result<Self, TemplateError> {
let path = path.as_ref();
let bytes = std::fs::read(path).map_err(|e| TemplateError::Io {
path: path.display().to_string(),
source: e,
})?;
let value: Value = serde_json::from_slice(&bytes)?;
Self::from_json(value)
}
pub fn render(&self, vars: &TemplateVars) -> Result<Value, TemplateError> {
render::render(self, vars)
}
pub fn validate(&self) -> Result<(), TemplateError> {
validate::validate(self)
}
}
#[derive(Debug, Clone, Default)]
pub struct TemplateVars {
vars: HashMap<String, Value>,
}
impl TemplateVars {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Value>) -> &mut Self {
self.vars.insert(key.into(), value.into());
self
}
pub fn insert_string(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.vars.insert(key.into(), Value::String(value.into()));
self
}
pub fn extend(&mut self, other: TemplateVars) {
self.vars.extend(other.vars);
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.vars.get(key)
}
pub fn contains(&self, key: &str) -> bool {
self.vars.contains_key(key)
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.vars.keys()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidTemplateRecord {
#[serde(flatten)]
pub template: DidTemplate,
pub scope: Scope,
pub created_at: u64,
pub updated_at: u64,
pub created_by: String,
}
#[derive(Debug, Error)]
pub enum TemplateError {
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("failed to read template file '{path}': {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error(
"unsupported schemaVersion {found} (this SDK supports {min}..={max}). Upgrade the SDK or downgrade the template."
)]
UnsupportedSchema { found: u32, min: u32, max: u32 },
#[error("invalid template: {0}")]
Invalid(String),
#[error("missing required variable(s): {0}. Supply with --var NAME=VALUE.")]
MissingVars(String),
#[error(
"unresolved placeholder(s) in rendered document: {0}. This is a bug in the template, not a missing --var."
)]
Unresolved(String),
#[error(
"reserved variable name '{0}' cannot appear in requiredVars/optionalVars — it is supplied automatically by the renderer"
)]
ReservedVar(String),
#[error(
"builtin template '{0}' not found (available: did-host-didcomm, did-host-http, did-host-http-didcomm, didcomm-mediator, vta-admin, vtc-host)"
)]
BuiltinNotFound(String),
}