1use crate::ids::{PublicName, PublicUuid};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(untagged, deny_unknown_fields)]
6pub enum SessionSelector {
7 Name {
8 name: PublicName,
9 },
10 Reference {
11 #[serde(rename = "ref")]
12 reference: SessionRef,
13 },
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct SessionRef {
19 pub deployment_id: PublicUuid,
20 pub runtime_login_id: PublicUuid,
21 pub session_id: PublicUuid,
22}
23
24impl SessionSelector {
25 pub fn name(&self) -> Option<&PublicName> {
26 match self {
27 Self::Name { name } => Some(name),
28 Self::Reference { .. } => None,
29 }
30 }
31}
32
33impl From<SessionRef> for SessionSelector {
34 fn from(reference: SessionRef) -> Self {
35 Self::Reference { reference }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(deny_unknown_fields)]
42pub struct ObjectRef {
43 pub datastore_id: PublicUuid,
44 pub kind: ObjectKind,
45 pub id: PublicUuid,
46}
47
48impl ObjectRef {
49 #[allow(clippy::result_large_err)]
51 pub fn validate_context(
52 self,
53 datastore_id: PublicUuid,
54 kind: ObjectKind,
55 ) -> Result<(), crate::ProtocolError> {
56 if self.kind != kind {
57 return Err(crate::ProtocolError::new(
58 crate::ProtocolErrorCode::ValidationFailed,
59 "Object reference has the wrong kind",
60 ));
61 }
62 if self.datastore_id != datastore_id {
63 return Err(crate::ProtocolError::new(
64 crate::ProtocolErrorCode::Conflict,
65 "Object reference belongs to another Datastore",
66 ));
67 }
68 Ok(())
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub enum ObjectKind {
75 Domain,
76 Study,
77 StudyType,
78 Asset,
79 AssetVersion,
80 Variable,
81 ValueType,
82 Vocabulary,
83 VocabularyItem,
84 VocabularyMapping,
85 Entity,
86 Relation,
87 EntityInstance,
88 RelationInstance,
89 Transformation,
90 Tag,
91 StudyAccessRequest,
92 Operation,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(untagged, deny_unknown_fields)]
97pub enum ObjectSelector {
98 Name {
99 name: PublicName,
100 },
101 Reference {
102 #[serde(rename = "ref")]
103 reference: ObjectRef,
104 },
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
108pub struct DatastoreRef {
109 pub id: PublicUuid,
110}
111
112pub type DomainRef = ObjectRef;
113
114pub type StudyRef = ObjectRef;
115
116pub type AssetRef = ObjectRef;
117
118pub type DataFileRef = ObjectRef;
119
120pub type DatasetRef = ObjectRef;
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
123pub struct ExportPackageRef {
124 pub id: PublicUuid,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
128pub struct TabularResultRef {
129 pub id: PublicUuid,
130}
131
132pub type VersionRef = ObjectRef;
133
134pub type VariableRef = ObjectRef;
135
136pub type VocabularyRef = ObjectRef;
137
138pub type TransformationRef = ObjectRef;
139
140pub type EntityRef = ObjectRef;
141
142pub type RelationRef = ObjectRef;
143
144pub type EntityInstanceRef = ObjectRef;
145pub type RelationInstanceRef = ObjectRef;
146pub type ValueTypeRef = ObjectRef;
147pub type VocabularyItemRef = ObjectRef;
148pub type VocabularyMappingRef = ObjectRef;
149
150pub type TagRef = ObjectRef;
151
152#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
153#[serde(tag = "kind", rename_all = "snake_case")]
154pub enum TagSelector {
155 Id { tag: TagRef },
156 Label { scope: TagScope, label: PublicName },
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[serde(rename_all = "snake_case")]
161pub enum TagScope {
162 Global,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct TagSummary {
167 pub tag: TagRef,
168 pub scope: TagScope,
169 pub label: String,
170}
171
172pub mod legacy_browser_public_id_namespace {
177 pub const DOMAIN: u128 = 0x0000_0000_0000_5000;
178 pub const TAG: u128 = 0x0000_0000_0000_5001;
179 pub const VARIABLE: u128 = 0x0000_0000_0000_5002;
180 pub const VOCABULARY: u128 = 0x0000_0000_0000_5003;
181 pub const VOCABULARY_ITEM: u128 = 0x0000_0000_0000_5004;
182 pub const VOCABULARY_MAPPING: u128 = 0x0000_0000_0000_5005;
183 pub const VALUE_TYPE: u128 = 0x0000_0000_0000_5006;
184 pub const ENTITY: u128 = 0x0000_0000_0000_5007;
185 pub const RELATION: u128 = 0x0000_0000_0000_5008;
186 pub const TRANSFORMATION: u128 = 0x0000_0000_0000_5009;
187}
188
189pub fn encode_scoped_integer_ref(scope: &str, id: i64) -> PublicUuid {
193 let mut bytes = [0_u8; 16];
194 bytes[..8].copy_from_slice(&stable_scope_hash(scope).to_be_bytes());
195 bytes[8..].copy_from_slice(&id.to_be_bytes());
196 PublicUuid::from_uuid(uuid::Uuid::from_bytes(bytes))
197}
198
199pub fn encode_legacy_namespaced_integer_ref(namespace: u128, id: i64) -> PublicUuid {
202 PublicUuid::from_uuid(uuid::Uuid::from_u128((namespace << 64) | id as u64 as u128))
203}
204
205pub fn decode_scoped_integer_ref(scope: &str, reference: PublicUuid) -> Option<i64> {
208 let bytes = reference.as_uuid().into_bytes();
209 let mut encoded_scope = [0_u8; 8];
210 encoded_scope.copy_from_slice(&bytes[..8]);
211 if u64::from_be_bytes(encoded_scope) != stable_scope_hash(scope) {
212 return None;
213 }
214 let mut id = [0_u8; 8];
215 id.copy_from_slice(&bytes[8..]);
216 Some(i64::from_be_bytes(id))
217}
218
219pub fn encode_scoped_string_ref(scope: &str, value: &str) -> PublicUuid {
221 let mut bytes = [0_u8; 16];
222 bytes[..8].copy_from_slice(&stable_scope_hash(scope).to_be_bytes());
223 bytes[8..].copy_from_slice(&stable_scope_hash(value).to_be_bytes());
224 PublicUuid::from_uuid(uuid::Uuid::from_bytes(bytes))
225}
226
227fn stable_scope_hash(scope: &str) -> u64 {
228 let mut hash = 0xcbf29ce484222325_u64;
229 for byte in scope.as_bytes() {
230 hash ^= u64::from(*byte);
231 hash = hash.wrapping_mul(0x100000001b3);
232 }
233 hash
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn scoped_integer_refs_round_trip_only_with_their_original_scope() {
242 let reference = encode_scoped_integer_ref("domain", -42);
243
244 assert_eq!(decode_scoped_integer_ref("domain", reference), Some(-42));
245 assert_eq!(decode_scoped_integer_ref("tag", reference), None);
246 }
247
248 #[test]
249 fn legacy_namespaced_integer_refs_remain_byte_stable() {
250 assert_eq!(
251 encode_legacy_namespaced_integer_ref(legacy_browser_public_id_namespace::DOMAIN, 42)
252 .to_string(),
253 "00000000-0000-5000-0000-00000000002a"
254 );
255 }
256
257 #[test]
258 fn legacy_browser_public_id_namespaces_remain_byte_stable() {
259 use legacy_browser_public_id_namespace::{
260 DOMAIN, ENTITY, RELATION, TAG, TRANSFORMATION, VALUE_TYPE, VARIABLE, VOCABULARY,
261 VOCABULARY_ITEM, VOCABULARY_MAPPING,
262 };
263
264 assert_eq!(
265 [
266 encode_legacy_namespaced_integer_ref(DOMAIN, 42).to_string(),
267 encode_legacy_namespaced_integer_ref(TAG, 42).to_string(),
268 encode_legacy_namespaced_integer_ref(VARIABLE, 42).to_string(),
269 encode_legacy_namespaced_integer_ref(VOCABULARY, 42).to_string(),
270 encode_legacy_namespaced_integer_ref(VOCABULARY_ITEM, 42).to_string(),
271 encode_legacy_namespaced_integer_ref(VOCABULARY_MAPPING, 42).to_string(),
272 encode_legacy_namespaced_integer_ref(VALUE_TYPE, 42).to_string(),
273 encode_legacy_namespaced_integer_ref(ENTITY, 42).to_string(),
274 encode_legacy_namespaced_integer_ref(RELATION, 42).to_string(),
275 encode_legacy_namespaced_integer_ref(TRANSFORMATION, 42).to_string(),
276 ],
277 [
278 "00000000-0000-5000-0000-00000000002a",
279 "00000000-0000-5001-0000-00000000002a",
280 "00000000-0000-5002-0000-00000000002a",
281 "00000000-0000-5003-0000-00000000002a",
282 "00000000-0000-5004-0000-00000000002a",
283 "00000000-0000-5005-0000-00000000002a",
284 "00000000-0000-5006-0000-00000000002a",
285 "00000000-0000-5007-0000-00000000002a",
286 "00000000-0000-5008-0000-00000000002a",
287 "00000000-0000-5009-0000-00000000002a",
288 ]
289 );
290 }
291
292 #[test]
293 fn scoped_string_refs_are_stable_and_scope_bound() {
294 let reference = encode_scoped_string_ref("datastore", "ahri-tre-test");
295
296 assert_eq!(
297 reference,
298 encode_scoped_string_ref("datastore", "ahri-tre-test")
299 );
300 assert_ne!(
301 reference,
302 encode_scoped_string_ref("deployment", "ahri-tre-test")
303 );
304 }
305}