use alloc::{format, string::String};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{cbor::canonical, cbor_bytes::CborBytes};
const SCHEMA_ID_PREFIX: &str = "schema:v1:";
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SchemaId(String);
impl SchemaId {
pub fn parse(value: &str) -> Result<Self, SchemaIdError> {
if !value.starts_with(SCHEMA_ID_PREFIX) {
return Err(SchemaIdError::InvalidPrefix);
}
let encoded = &value[SCHEMA_ID_PREFIX.len()..];
canonical::decode_base32_crockford(encoded)?;
Ok(Self(value.to_owned()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for SchemaId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}
pub fn schema_id_for_cbor(schema_cbor: &[u8]) -> Result<SchemaId, SchemaIdError> {
canonical::ensure_canonical(schema_cbor)?;
let digest = canonical::blake3_128(schema_cbor);
let encoded = canonical::encode_base32_crockford(&digest);
Ok(SchemaId(format!("{SCHEMA_ID_PREFIX}{encoded}")))
}
#[derive(Debug, Error)]
pub enum SchemaIdError {
#[error("schema ID must begin with {SCHEMA_ID_PREFIX}")]
InvalidPrefix,
#[error("invalid base32 payload: {0}")]
Base32(#[from] canonical::Base32Error),
#[error(transparent)]
Canonical(#[from] canonical::CanonicalError),
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SchemaSource {
CborSchemaId(SchemaId),
InlineCbor(CborBytes),
#[cfg(feature = "json-compat")]
InlineJson(String),
RefPackPath(String),
RefUri(String),
}
pub type QaSchemaSource = SchemaSource;
pub type IoSchemaSource = SchemaSource;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_id_roundtrip() {
let schema = canon_schema();
let id = match schema_id_for_cbor(&schema) {
Ok(value) => value,
Err(err) => panic!("id generation failed: {err:?}"),
};
let parsed = match SchemaId::parse(id.as_str()) {
Ok(value) => value,
Err(err) => panic!("parse failed: {err:?}"),
};
assert_eq!(parsed.as_str(), id.as_str());
}
fn canon_schema() -> Vec<u8> {
match canonical::to_canonical_cbor(&"schema") {
Ok(bytes) => bytes,
Err(err) => panic!("canonicalize schema failed: {err:?}"),
}
}
}