use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::ContractError;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ContractMetadata {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub digest: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub annotations: BTreeMap<String, String>,
}
impl ContractMetadata {
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Self::default()
}
}
pub(crate) fn validate(&self) -> Result<(), ContractError> {
let name = self.name.trim();
if name.is_empty() {
return Err(ContractError::invalid("metadata.name must not be empty"));
}
if name.len() > 256 || name.chars().any(char::is_control) {
return Err(ContractError::invalid(
"metadata.name must be at most 256 bytes and contain no control characters",
));
}
if let Some(version) = &self.version
&& (version.trim().is_empty() || version.chars().any(char::is_control))
{
return Err(ContractError::invalid(
"metadata.version must be non-empty and contain no control characters",
));
}
if let Some(digest) = &self.digest {
let Some(hex) = digest.strip_prefix("sha256:") else {
return Err(ContractError::invalid(
"metadata.digest must use the sha256:<hex> form",
));
};
if hex.len() != 64 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(ContractError::invalid(
"metadata.digest must contain exactly 64 hexadecimal digits",
));
}
}
Ok(())
}
}