1use core::marker::PhantomData;
4use std::sync::Arc;
5
6use crate::error::{Error, Result};
7
8#[doc(hidden)]
9pub mod sealed {
10 pub trait Sealed {}
11}
12
13pub trait Schema: sealed::Sealed + Send + Sync + 'static {}
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct Unbound;
19
20impl sealed::Sealed for Unbound {}
21impl Schema for Unbound {}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct SchemaPackage<S: Schema> {
26 semantic_fingerprint_json: &'static str,
27 projection_fingerprint_json: &'static str,
28 runtime_projection_json: &'static str,
29 declared_schema_json: Option<&'static str>,
30 marker: PhantomData<fn() -> S>,
31}
32
33impl<S: Schema> SchemaPackage<S> {
34 #[doc(hidden)]
36 #[must_use]
37 pub const fn new(
38 semantic_fingerprint_json: &'static str,
39 projection_fingerprint_json: &'static str,
40 runtime_projection_json: &'static str,
41 ) -> Self {
42 Self {
43 semantic_fingerprint_json,
44 projection_fingerprint_json,
45 runtime_projection_json,
46 declared_schema_json: None,
47 marker: PhantomData,
48 }
49 }
50
51 #[doc(hidden)]
54 #[must_use]
55 pub const fn new_with_declared(
56 semantic_fingerprint_json: &'static str,
57 projection_fingerprint_json: &'static str,
58 runtime_projection_json: &'static str,
59 declared_schema_json: &'static str,
60 ) -> Self {
61 Self {
62 semantic_fingerprint_json,
63 projection_fingerprint_json,
64 runtime_projection_json,
65 declared_schema_json: Some(declared_schema_json),
66 marker: PhantomData,
67 }
68 }
69
70 pub fn verify(&self) -> Result<()> {
72 let _ = type_bridge_orm::InstalledRuntimeProjection::from_verified_rust_json(
73 self.runtime_projection_json.as_bytes(),
74 self.semantic_fingerprint_json.as_bytes(),
75 self.projection_fingerprint_json.as_bytes(),
76 )
77 .map_err(|err| Error::SchemaVerification {
78 message: err.to_string(),
79 source: Some(Box::new(err)),
80 })?;
81 Ok(())
82 }
83
84 #[doc(hidden)]
86 #[must_use]
87 pub const fn semantic_fingerprint_json(&self) -> &'static str {
88 self.semantic_fingerprint_json
89 }
90
91 #[doc(hidden)]
93 #[must_use]
94 pub const fn projection_fingerprint_json(&self) -> &'static str {
95 self.projection_fingerprint_json
96 }
97
98 #[doc(hidden)]
100 #[must_use]
101 pub const fn runtime_projection_json(&self) -> &'static str {
102 self.runtime_projection_json
103 }
104
105 pub(crate) const fn declared_schema_json(&self) -> Option<&'static str> {
106 self.declared_schema_json
107 }
108
109 pub(crate) fn verify_and_install(
111 &self,
112 ) -> Result<Arc<type_bridge_orm::InstalledRuntimeProjection>> {
113 let projection = type_bridge_orm::InstalledRuntimeProjection::from_verified_rust_json(
114 self.runtime_projection_json.as_bytes(),
115 self.semantic_fingerprint_json.as_bytes(),
116 self.projection_fingerprint_json.as_bytes(),
117 )
118 .map_err(|err| Error::SchemaVerification {
119 message: err.to_string(),
120 source: Some(Box::new(err)),
121 })?;
122 Ok(Arc::new(projection))
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use type_bridge_contract::codec::to_canonical_json;
130 use type_bridge_contract::fingerprint::SemanticProfileId;
131 use type_bridge_contract::projection::{BindingTarget, ProjectionConfig};
132 use type_bridge_contract::schema::DocumentId;
133 use type_bridge_schema::{SchemaDocumentSet, normalize_documents, project, resolve};
134 use type_bridge_schema_codegen::{PythonEmitter, RustEmitter};
135
136 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
137 struct TestSchema;
138 impl sealed::Sealed for TestSchema {}
139 impl Schema for TestSchema {}
140
141 #[test]
142 fn schema_package_fingerprint_verification() {
143 let documents = SchemaDocumentSet::parse([(
144 DocumentId::new("test.yaml").unwrap(),
145 "format: typebridge.schema/v2\nentities:\n person: {}\n",
146 )])
147 .unwrap();
148 let declared = normalize_documents(&documents).unwrap();
149 let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
150 let resolved = resolve(&declared, &profile).unwrap();
151 let emitter = RustEmitter::new();
152 let resources = emitter.code_resources().unwrap();
153 let projection = project(
154 &resolved,
155 BindingTarget::Rust,
156 &ProjectionConfig::rust(),
157 &emitter.generator_handlers(),
158 &resources,
159 )
160 .unwrap();
161
162 let semantic_json =
163 String::from_utf8(to_canonical_json(projection.semantic_fingerprint()).unwrap())
164 .unwrap();
165 let projection_json =
166 String::from_utf8(to_canonical_json(projection.projection_fingerprint()).unwrap())
167 .unwrap();
168 let runtime_json = String::from_utf8(to_canonical_json(&projection).unwrap()).unwrap();
169
170 let semantic_ref: &'static str = Box::leak(semantic_json.into_boxed_str());
171 let projection_ref: &'static str = Box::leak(projection_json.into_boxed_str());
172 let runtime_ref: &'static str = Box::leak(runtime_json.into_boxed_str());
173
174 let valid_package: SchemaPackage<TestSchema> =
175 SchemaPackage::new(semantic_ref, projection_ref, runtime_ref);
176 assert!(valid_package.verify().is_ok());
177
178 let tampered_package: SchemaPackage<TestSchema> =
179 SchemaPackage::new(semantic_ref, r#""rust/v1-tampered""#, runtime_ref);
180 match tampered_package.verify() {
181 Err(err) => {
182 use std::error::Error as _;
183 assert!(err.source().is_some());
184 }
185 Ok(_) => panic!("tampered schema package must fail verification"),
186 }
187 }
188
189 #[test]
190 fn rejects_non_rust_target_projection() {
191 let documents = SchemaDocumentSet::parse([(
192 DocumentId::new("test.yaml").unwrap(),
193 "format: typebridge.schema/v2\nentities:\n person: {}\n",
194 )])
195 .unwrap();
196 let declared = normalize_documents(&documents).unwrap();
197 let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
198 let resolved = resolve(&declared, &profile).unwrap();
199 let py_emitter = PythonEmitter::new();
200 let py_resources = py_emitter.code_resources().unwrap();
201 let py_projection = project(
202 &resolved,
203 BindingTarget::Python,
204 &ProjectionConfig::python(),
205 &py_emitter.generator_handlers(),
206 &py_resources,
207 )
208 .unwrap();
209
210 let semantic_json =
211 String::from_utf8(to_canonical_json(py_projection.semantic_fingerprint()).unwrap())
212 .unwrap();
213 let projection_json =
214 String::from_utf8(to_canonical_json(py_projection.projection_fingerprint()).unwrap())
215 .unwrap();
216 let runtime_json = String::from_utf8(to_canonical_json(&py_projection).unwrap()).unwrap();
217
218 let semantic_ref: &'static str = Box::leak(semantic_json.into_boxed_str());
219 let projection_ref: &'static str = Box::leak(projection_json.into_boxed_str());
220 let runtime_ref: &'static str = Box::leak(runtime_json.into_boxed_str());
221
222 let py_package: SchemaPackage<TestSchema> =
223 SchemaPackage::new(semantic_ref, projection_ref, runtime_ref);
224 let err = py_package.verify().unwrap_err();
225 assert!(err.to_string().contains("target mismatch") || err.to_string().contains("Rust"));
226 }
227}