Skip to main content

type_bridge/
schema.rs

1//! Schema package markers and type-branded installation handshake.
2
3use 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
13/// A type-level marker representing a generated schema package.
14pub trait Schema: sealed::Sealed + Send + Sync + 'static {}
15
16/// Default marker representing an unbound database handle.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct Unbound;
19
20impl sealed::Sealed for Unbound {}
21impl Schema for Unbound {}
22
23/// A generated schema package marker carrying fingerprint evidence branded by `S: Schema`.
24#[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    /// Construct a type-branded schema package marker from verified JSON evidence (generated-code SPI).
35    #[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    /// Construct a generated package carrying canonical remote query
52    /// authority in addition to verified runtime projection evidence.
53    #[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    /// Perform offline fingerprint verification without connecting to a live server.
71    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    /// Return the semantic schema fingerprint JSON string (generated-code SPI).
85    #[doc(hidden)]
86    #[must_use]
87    pub const fn semantic_fingerprint_json(&self) -> &'static str {
88        self.semantic_fingerprint_json
89    }
90
91    /// Return the binding target projection fingerprint JSON string (generated-code SPI).
92    #[doc(hidden)]
93    #[must_use]
94    pub const fn projection_fingerprint_json(&self) -> &'static str {
95        self.projection_fingerprint_json
96    }
97
98    /// Return the canonical runtime projection JSON string (generated-code SPI).
99    #[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    /// Perform runtime fingerprint verification and derive provider descriptors (crate-internal).
110    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}