type_bridge_contract/
schema_lowering.rs1use std::error::Error;
4use std::fmt;
5
6use serde::de::Error as _;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::codec::from_canonical_json;
10use crate::diagnostic::{Diagnostic, DiagnosticCategory};
11use crate::fingerprint::{
12 CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
13};
14
15pub const TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID: &str = "typedb-3.12.1-schema-lowering/v1";
17pub const SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN: &str = "typebridge.schema.lowering-profile";
19pub const SCHEMA_LOWERING_PROFILE_CANONICALIZATION: &str = "typebridge.schema-lowering-profile/v1";
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct SchemaLoweringProfileValidationError {
25 message: String,
26}
27
28impl SchemaLoweringProfileValidationError {
29 fn new(message: impl Into<String>) -> Self {
30 Self {
31 message: message.into(),
32 }
33 }
34}
35
36impl fmt::Display for SchemaLoweringProfileValidationError {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 formatter.write_str(&self.message)
39 }
40}
41
42impl Error for SchemaLoweringProfileValidationError {}
43
44#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
46pub struct SchemaLoweringProfileId(String);
47
48impl SchemaLoweringProfileId {
49 pub fn new(value: impl Into<String>) -> Result<Self, SchemaLoweringProfileValidationError> {
51 let value = value.into();
52 SemanticProfileId::new(value.clone()).map_err(|error| {
53 SchemaLoweringProfileValidationError::new(format!(
54 "invalid schema-lowering profile id: {error}"
55 ))
56 })?;
57 if value != TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID {
58 return Err(SchemaLoweringProfileValidationError::new(format!(
59 "unsupported schema-lowering profile id: {value}"
60 )));
61 }
62 Ok(Self(value))
63 }
64
65 pub fn typedb_3_12_1() -> Self {
67 Self::new(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
68 .expect("the fixed schema-lowering profile id is valid")
69 }
70
71 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75}
76
77impl fmt::Display for SchemaLoweringProfileId {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 formatter.write_str(self.as_str())
80 }
81}
82
83impl Serialize for SchemaLoweringProfileId {
84 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85 where
86 S: Serializer,
87 {
88 serializer.serialize_str(self.as_str())
89 }
90}
91
92impl<'de> Deserialize<'de> for SchemaLoweringProfileId {
93 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94 where
95 D: Deserializer<'de>,
96 {
97 let value = String::deserialize(deserializer)?;
98 Self::new(value).map_err(D::Error::custom)
99 }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct SchemaLoweringProfileFingerprint(Fingerprint);
105
106impl SchemaLoweringProfileFingerprint {
107 pub fn compute(canonical_bytes: &[u8]) -> Self {
109 let domain = FingerprintDomain::new(SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN)
110 .expect("the fixed schema-lowering fingerprint domain is valid");
111 let canonicalization =
112 CanonicalizationVersion::new(SCHEMA_LOWERING_PROFILE_CANONICALIZATION)
113 .expect("the fixed schema-lowering canonicalization is valid");
114 let profile = SemanticProfileId::new(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
115 .expect("the fixed schema-lowering profile id is a valid semantic profile id");
116 Self(Fingerprint::compute(
117 domain,
118 canonicalization,
119 Some(profile),
120 canonical_bytes,
121 ))
122 }
123
124 pub fn as_fingerprint(&self) -> &Fingerprint {
126 &self.0
127 }
128
129 fn validate_metadata(
130 fingerprint: &Fingerprint,
131 ) -> Result<(), SchemaLoweringProfileValidationError> {
132 let value = serde_json::to_value(fingerprint).map_err(|error| {
133 SchemaLoweringProfileValidationError::new(format!(
134 "cannot inspect schema-lowering fingerprint: {error}"
135 ))
136 })?;
137 let expected = [
138 ("domain", SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN),
139 ("canonicalization", SCHEMA_LOWERING_PROFILE_CANONICALIZATION),
140 ("semantic_profile", TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID),
141 ];
142 for (field, expected_value) in expected {
143 if value.get(field).and_then(serde_json::Value::as_str) != Some(expected_value) {
144 return Err(SchemaLoweringProfileValidationError::new(format!(
145 "schema-lowering fingerprint has invalid {field}"
146 )));
147 }
148 }
149 Ok(())
150 }
151}
152
153impl Serialize for SchemaLoweringProfileFingerprint {
154 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: Serializer,
157 {
158 self.0.serialize(serializer)
159 }
160}
161
162impl<'de> Deserialize<'de> for SchemaLoweringProfileFingerprint {
163 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
164 where
165 D: Deserializer<'de>,
166 {
167 let fingerprint = Fingerprint::deserialize(deserializer)?;
168 Self::validate_metadata(&fingerprint).map_err(D::Error::custom)?;
169 Ok(Self(fingerprint))
170 }
171}
172
173#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
175pub struct SchemaLoweringProfileBinding {
176 fingerprint: SchemaLoweringProfileFingerprint,
177 id: SchemaLoweringProfileId,
178}
179
180impl SchemaLoweringProfileBinding {
181 pub fn from_canonical_profile_bytes(
186 canonical_profile_bytes: &[u8],
187 ) -> Result<Self, Diagnostic> {
188 let value: serde_json::Value = from_canonical_json(canonical_profile_bytes)?;
189 if value.get("id").and_then(serde_json::Value::as_str)
190 != Some(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
191 {
192 return Err(Diagnostic::stable(
193 DiagnosticCategory::InvalidContract,
194 "schema_lowering_profile_id_mismatch",
195 "canonical schema-lowering profile bytes do not carry the frozen profile id",
196 ));
197 }
198 Ok(Self {
199 fingerprint: SchemaLoweringProfileFingerprint::compute(canonical_profile_bytes),
200 id: SchemaLoweringProfileId::typedb_3_12_1(),
201 })
202 }
203
204 pub const fn id(&self) -> &SchemaLoweringProfileId {
206 &self.id
207 }
208
209 pub const fn fingerprint(&self) -> &SchemaLoweringProfileFingerprint {
211 &self.fingerprint
212 }
213}