1use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5use std::collections::BTreeMap;
6use std::fmt::{Display, Formatter};
7use std::hash::{Hash, Hasher};
8use std::marker::PhantomData;
9
10macro_rules! define_id {
11 ($name:ident) => {
12 #[derive(
13 Clone,
14 Copy,
15 Debug,
16 Default,
17 Deserialize,
18 Eq,
19 Hash,
20 Ord,
21 PartialEq,
22 PartialOrd,
23 Serialize,
24 )]
25 #[serde(transparent)]
26 pub struct $name(pub u64);
27
28 impl $name {
29 #[must_use]
30 pub const fn new(value: u64) -> Self {
31 Self(value)
32 }
33
34 #[must_use]
35 pub const fn get(self) -> u64 {
36 self.0
37 }
38 }
39
40 impl Display for $name {
41 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
42 Display::fmt(&self.0, formatter)
43 }
44 }
45 };
46}
47
48define_id!(ArmyId);
49define_id!(BoundaryId);
50define_id!(CommandAttemptId);
51define_id!(CommandId);
52define_id!(CommandRequestId);
53define_id!(EventId);
54define_id!(GovernmentId);
55define_id!(IngressId);
56define_id!(OrganizationId);
57define_id!(PersonId);
58define_id!(RandomDrawId);
59define_id!(ResourceId);
60define_id!(RouteId);
61define_id!(TerritoryId);
62
63#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
66pub struct DomainRecordKind {
67 pub namespace: String,
68 pub name: String,
69}
70
71impl DomainRecordKind {
72 #[must_use]
73 pub fn new(namespace: impl Into<String>, name: impl Into<String>) -> Self {
74 Self {
75 namespace: namespace.into(),
76 name: name.into(),
77 }
78 }
79
80 #[must_use]
81 pub fn for_type<T: DomainRecordType>() -> Self {
82 Self::new(T::NAMESPACE, T::NAME)
83 }
84
85 #[must_use]
86 pub fn matches_type<T: DomainRecordType>(&self) -> bool {
87 self.namespace == T::NAMESPACE && self.name == T::NAME
88 }
89}
90
91impl Display for DomainRecordKind {
92 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
93 write!(formatter, "{}.{}", self.namespace, self.name)
94 }
95}
96
97#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
99pub struct DomainRecordRef {
100 pub kind: DomainRecordKind,
101 pub id: String,
102}
103
104impl DomainRecordRef {
105 #[must_use]
106 pub fn new(
107 namespace: impl Into<String>,
108 kind: impl Into<String>,
109 id: impl Into<String>,
110 ) -> Self {
111 Self {
112 kind: DomainRecordKind::new(namespace, kind),
113 id: id.into(),
114 }
115 }
116}
117
118impl Display for DomainRecordRef {
119 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
120 write!(formatter, "{}:{}", self.kind, self.id)
121 }
122}
123
124pub trait DomainRecordType {
130 type Payload;
131 type Class: DomainKindClass;
132
133 const NAMESPACE: &'static str;
134 const NAME: &'static str;
135}
136
137mod domain_kind_class {
138 pub trait Sealed {}
139}
140
141pub trait DomainKindClass: domain_kind_class::Sealed {
143 const IS_ENTITY: bool;
144}
145
146pub enum DomainEntityKindClass {}
148
149impl domain_kind_class::Sealed for DomainEntityKindClass {}
150
151impl DomainKindClass for DomainEntityKindClass {
152 const IS_ENTITY: bool = true;
153}
154
155pub enum DomainValueKindClass {}
157
158impl domain_kind_class::Sealed for DomainValueKindClass {}
159
160impl DomainKindClass for DomainValueKindClass {
161 const IS_ENTITY: bool = false;
162}
163
164pub trait DomainEntityType: DomainRecordType<Class = DomainEntityKindClass> {}
166
167impl<T: DomainRecordType<Class = DomainEntityKindClass>> DomainEntityType for T {}
168
169pub trait DomainValueType: DomainRecordType<Class = DomainValueKindClass> {}
171
172impl<T: DomainRecordType<Class = DomainValueKindClass>> DomainValueType for T {}
173
174#[derive(Serialize)]
179#[serde(transparent, bound = "")]
180pub struct TypedDomainRecordRef<T: DomainRecordType> {
181 reference: DomainRecordRef,
182 #[serde(skip)]
183 marker: PhantomData<fn() -> T>,
184}
185
186impl<T: DomainRecordType> Clone for TypedDomainRecordRef<T> {
187 fn clone(&self) -> Self {
188 Self {
189 reference: self.reference.clone(),
190 marker: PhantomData,
191 }
192 }
193}
194
195impl<T: DomainRecordType> std::fmt::Debug for TypedDomainRecordRef<T> {
196 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
197 formatter
198 .debug_tuple("TypedDomainRecordRef")
199 .field(&self.reference)
200 .finish()
201 }
202}
203
204impl<T: DomainRecordType> PartialEq for TypedDomainRecordRef<T> {
205 fn eq(&self, other: &Self) -> bool {
206 self.reference == other.reference
207 }
208}
209
210impl<T: DomainRecordType> Eq for TypedDomainRecordRef<T> {}
211
212impl<T: DomainRecordType> PartialOrd for TypedDomainRecordRef<T> {
213 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
214 Some(self.cmp(other))
215 }
216}
217
218impl<T: DomainRecordType> Ord for TypedDomainRecordRef<T> {
219 fn cmp(&self, other: &Self) -> Ordering {
220 self.reference.cmp(&other.reference)
221 }
222}
223
224impl<T: DomainRecordType> Hash for TypedDomainRecordRef<T> {
225 fn hash<H: Hasher>(&self, state: &mut H) {
226 self.reference.hash(state);
227 }
228}
229
230impl<'de, T: DomainRecordType> Deserialize<'de> for TypedDomainRecordRef<T> {
231 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232 where
233 D: serde::Deserializer<'de>,
234 {
235 let reference = DomainRecordRef::deserialize(deserializer)?;
236 Self::from_untyped(reference).map_err(|reference| {
237 serde::de::Error::custom(format!(
238 "domain record reference {reference} does not match typed kind {}",
239 DomainRecordKind::for_type::<T>()
240 ))
241 })
242 }
243}
244
245impl<T: DomainRecordType> TypedDomainRecordRef<T> {
246 #[must_use]
247 pub fn new(id: impl Into<String>) -> Self {
248 Self {
249 reference: DomainRecordRef {
250 kind: DomainRecordKind::for_type::<T>(),
251 id: id.into(),
252 },
253 marker: PhantomData,
254 }
255 }
256
257 #[must_use]
258 pub const fn as_untyped(&self) -> &DomainRecordRef {
259 &self.reference
260 }
261
262 #[must_use]
263 pub fn into_untyped(self) -> DomainRecordRef {
264 self.reference
265 }
266
267 pub fn from_untyped(reference: DomainRecordRef) -> Result<Self, DomainRecordRef> {
273 if !reference.kind.matches_type::<T>() {
274 return Err(reference);
275 }
276 Ok(Self {
277 reference,
278 marker: PhantomData,
279 })
280 }
281
282 #[must_use]
283 pub fn id(&self) -> &str {
284 &self.reference.id
285 }
286}
287
288impl<T: DomainRecordType> Display for TypedDomainRecordRef<T> {
289 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
290 Display::fmt(&self.reference, formatter)
291 }
292}
293
294impl<T: DomainRecordType> From<TypedDomainRecordRef<T>> for DomainRecordRef {
295 fn from(reference: TypedDomainRecordRef<T>) -> Self {
296 reference.into_untyped()
297 }
298}
299
300impl<T: DomainEntityType> From<TypedDomainRecordRef<T>> for EntityRef {
301 fn from(reference: TypedDomainRecordRef<T>) -> Self {
302 Self::Domain(reference.into_untyped())
303 }
304}
305
306#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
307#[serde(rename_all = "snake_case")]
308pub enum CoreEntityKind {
309 Army,
310 Government,
311 Organization,
312 Person,
313 Resource,
314 Route,
315 Territory,
316}
317
318#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
320#[serde(tag = "type", content = "id", rename_all = "snake_case")]
321pub enum EntityRef {
322 Army(ArmyId),
323 Domain(DomainRecordRef),
324 Government(GovernmentId),
325 Organization(OrganizationId),
326 Person(PersonId),
327 Resource(ResourceId),
328 Route(RouteId),
329 Territory(TerritoryId),
330}
331
332impl Display for EntityRef {
333 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
334 match self {
335 Self::Army(id) => write!(formatter, "army:{id}"),
336 Self::Domain(reference) => write!(formatter, "domain:{reference}"),
337 Self::Government(id) => write!(formatter, "government:{id}"),
338 Self::Organization(id) => write!(formatter, "organization:{id}"),
339 Self::Person(id) => write!(formatter, "person:{id}"),
340 Self::Resource(id) => write!(formatter, "resource:{id}"),
341 Self::Route(id) => write!(formatter, "route:{id}"),
342 Self::Territory(id) => write!(formatter, "territory:{id}"),
343 }
344 }
345}
346
347impl EntityRef {
348 #[must_use]
349 pub const fn core_kind(&self) -> Option<CoreEntityKind> {
350 match self {
351 Self::Army(_) => Some(CoreEntityKind::Army),
352 Self::Domain(_) => None,
353 Self::Government(_) => Some(CoreEntityKind::Government),
354 Self::Organization(_) => Some(CoreEntityKind::Organization),
355 Self::Person(_) => Some(CoreEntityKind::Person),
356 Self::Resource(_) => Some(CoreEntityKind::Resource),
357 Self::Route(_) => Some(CoreEntityKind::Route),
358 Self::Territory(_) => Some(CoreEntityKind::Territory),
359 }
360 }
361}
362
363#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
366pub struct DeterministicRng {
367 state: u64,
368}
369
370impl DeterministicRng {
371 const STEP: u64 = 0x9E37_79B9_7F4A_7C15;
372
373 #[must_use]
374 pub const fn from_seed(seed: u64) -> Self {
375 Self { state: seed }
376 }
377
378 #[must_use]
379 pub const fn state(self) -> u64 {
380 self.state
381 }
382
383 #[must_use]
384 pub const fn state_after(seed: u64, draws: u64) -> u64 {
385 seed.wrapping_add(Self::STEP.wrapping_mul(draws))
386 }
387
388 #[must_use]
389 pub const fn seed_before(state: u64, draws: u64) -> u64 {
390 state.wrapping_sub(Self::STEP.wrapping_mul(draws))
391 }
392
393 pub fn next_u64(&mut self) -> u64 {
394 self.state = self.state.wrapping_add(Self::STEP);
395 let mut value = self.state;
396 value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
397 value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
398 value ^ (value >> 31)
399 }
400
401 pub fn range(&mut self, upper_exclusive: u64) -> u64 {
403 if upper_exclusive == 0 {
404 return 0;
405 }
406 self.next_u64() % upper_exclusive
407 }
408}
409
410#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
411pub struct FieldSchema {
412 pub name: String,
413 pub value_type: String,
414 pub description: String,
415 pub reference_type: Option<String>,
416 pub writable_via_debug_command: bool,
417}
418
419#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
420pub struct TypeSchema {
421 pub type_name: String,
422 pub description: String,
423 pub fields: Vec<FieldSchema>,
424}
425
426#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
427pub struct SchemaRegistry {
428 types: BTreeMap<String, TypeSchema>,
429}
430
431impl SchemaRegistry {
432 pub fn register(&mut self, schema: TypeSchema) {
433 self.types.insert(schema.type_name.clone(), schema);
434 }
435
436 #[must_use]
437 pub fn get(&self, type_name: &str) -> Option<&TypeSchema> {
438 self.types.get(type_name)
439 }
440
441 pub fn iter(&self) -> impl Iterator<Item = &TypeSchema> {
442 self.types.values()
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 struct Office;
451
452 impl DomainRecordType for Office {
453 type Payload = String;
454 type Class = DomainEntityKindClass;
455
456 const NAMESPACE: &'static str = "fixture.governance";
457 const NAME: &'static str = "office";
458 }
459
460 struct Obligation;
461
462 impl DomainRecordType for Obligation {
463 type Payload = String;
464 type Class = DomainValueKindClass;
465
466 const NAMESPACE: &'static str = "fixture.governance";
467 const NAME: &'static str = "obligation";
468 }
469
470 #[test]
471 fn typed_domain_identity_preserves_wire_shape_and_kind_boundary() {
472 let typed = TypedDomainRecordRef::<Office>::new("secretariat");
473 let raw = DomainRecordRef::new("fixture.governance", "office", "secretariat");
474
475 assert_eq!(
476 serde_json::to_value(&typed).expect("typed identity should serialize"),
477 serde_json::to_value(&raw).expect("raw identity should serialize")
478 );
479 let round_trip: TypedDomainRecordRef<Office> = serde_json::from_value(
480 serde_json::to_value(&typed).expect("typed identity should serialize"),
481 )
482 .expect("typed identity should deserialize");
483 assert_eq!(round_trip.as_untyped(), &raw);
484 assert_eq!(EntityRef::from(round_trip), EntityRef::Domain(raw.clone()));
485
486 let wrong_kind =
487 DomainRecordRef::new("fixture.governance", "obligation", "secretariat-duty");
488 assert_eq!(
489 TypedDomainRecordRef::<Office>::from_untyped(wrong_kind.clone()),
490 Err(wrong_kind)
491 );
492 assert!(TypedDomainRecordRef::<Obligation>::from_untyped(raw).is_err());
493 assert!(
494 serde_json::from_value::<TypedDomainRecordRef<Office>>(serde_json::json!({
495 "kind": {
496 "namespace": "fixture.governance",
497 "name": "obligation"
498 },
499 "id": "secretariat-duty"
500 }))
501 .is_err()
502 );
503 }
504}