greentic_types/
schema_id.rs1use alloc::{format, string::String};
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6use crate::{cbor::canonical, cbor_bytes::CborBytes};
7
8const SCHEMA_ID_PREFIX: &str = "schema:v1:";
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub struct SchemaId(String);
14
15impl SchemaId {
16 pub fn parse(value: &str) -> Result<Self, SchemaIdError> {
18 if !value.starts_with(SCHEMA_ID_PREFIX) {
19 return Err(SchemaIdError::InvalidPrefix);
20 }
21 let encoded = &value[SCHEMA_ID_PREFIX.len()..];
22 canonical::decode_base32_crockford(encoded)?;
23 Ok(Self(value.to_owned()))
24 }
25
26 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32impl core::fmt::Display for SchemaId {
33 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34 f.write_str(&self.0)
35 }
36}
37
38pub fn schema_id_for_cbor(schema_cbor: &[u8]) -> Result<SchemaId, SchemaIdError> {
40 canonical::ensure_canonical(schema_cbor)?;
41 let digest = canonical::blake3_128(schema_cbor);
42 let encoded = canonical::encode_base32_crockford(&digest);
43 Ok(SchemaId(format!("{SCHEMA_ID_PREFIX}{encoded}")))
44}
45
46#[derive(Debug, Error)]
48pub enum SchemaIdError {
49 #[error("schema ID must begin with {SCHEMA_ID_PREFIX}")]
51 InvalidPrefix,
52 #[error("invalid base32 payload: {0}")]
54 Base32(#[from] canonical::Base32Error),
55 #[error(transparent)]
57 Canonical(#[from] canonical::CanonicalError),
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
63pub enum SchemaSource {
64 CborSchemaId(SchemaId),
66 InlineCbor(CborBytes),
68 #[cfg(feature = "json-compat")]
70 InlineJson(String),
71 RefPackPath(String),
73 RefUri(String),
75}
76
77pub type QaSchemaSource = SchemaSource;
79pub type IoSchemaSource = SchemaSource;
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn schema_id_roundtrip() {
88 let schema = canon_schema();
89 let id = match schema_id_for_cbor(&schema) {
90 Ok(value) => value,
91 Err(err) => panic!("id generation failed: {err:?}"),
92 };
93 let parsed = match SchemaId::parse(id.as_str()) {
94 Ok(value) => value,
95 Err(err) => panic!("parse failed: {err:?}"),
96 };
97 assert_eq!(parsed.as_str(), id.as_str());
98 }
99
100 fn canon_schema() -> Vec<u8> {
101 match canonical::to_canonical_cbor(&"schema") {
102 Ok(bytes) => bytes,
103 Err(err) => panic!("canonicalize schema failed: {err:?}"),
104 }
105 }
106}