Skip to main content

ant_types/
ids.rs

1use serde::{Deserialize, Serialize};
2
3/// Tenant identifier. Tenants are the top-level isolation boundary.
4#[derive(
5    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
6)]
7#[serde(transparent)]
8pub struct TenantId(pub u64);
9
10// `#[serde(transparent)]` newtypes serialize as their inner value, so
11// the schema has to say `integer` and not `object` — the derive would
12// otherwise disagree with the wire.
13#[cfg(feature = "utoipa")]
14impl<'s> utoipa::ToSchema<'s> for TenantId {
15    fn schema() -> (&'s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
16        ("TenantId", transparent_u64_schema("Tenant identifier."))
17    }
18}
19
20/// Project identifier. A project lives under one tenant and has its own
21/// schema namespace.
22#[derive(
23    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
24)]
25#[serde(transparent)]
26pub struct ProjectId(pub u64);
27
28#[cfg(feature = "utoipa")]
29impl<'s> utoipa::ToSchema<'s> for ProjectId {
30    fn schema() -> (&'s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
31        ("ProjectId", transparent_u64_schema("Project identifier."))
32    }
33}
34
35#[cfg(feature = "utoipa")]
36fn transparent_u64_schema(description: &str) -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
37    use utoipa::openapi::schema::{ObjectBuilder, SchemaFormat, SchemaType};
38    ObjectBuilder::new()
39        .schema_type(SchemaType::Integer)
40        .format(Some(SchemaFormat::KnownFormat(
41            utoipa::openapi::KnownFormat::Int64,
42        )))
43        .minimum(Some(0.0))
44        .description(Some(description))
45        .into()
46}
47
48/// Project namespace (e.g. "Antares"). Prepended to all type names at the
49/// SPG layer (`Antares.Deal`).
50#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
51#[serde(transparent)]
52pub struct Namespace(pub String);
53
54/// Namespace-qualified SPG type name, e.g. `Antares.Deal` or `Antares.Chunk`.
55#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
56#[serde(transparent)]
57pub struct TypeName(pub String);
58
59impl TypeName {
60    /// Build a namespace-qualified type name from an unqualified label.
61    pub fn qualified(ns: &Namespace, label: &str) -> Self {
62        if label.contains('.') {
63            // Already qualified.
64            Self(label.to_owned())
65        } else {
66            Self(format!("{}.{}", ns.0, label))
67        }
68    }
69}
70
71/// Vertex business id (the `id` field on the wire, e.g. "deal_hooli_001").
72#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
73#[serde(transparent)]
74pub struct VertexId(pub String);
75
76/// Edge id (assigned by the caller in `writerGraph`, e.g. "e1").
77#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
78#[serde(transparent)]
79pub struct EdgeId(pub String);
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn qualified_name_prepends_namespace() {
87        let ns = Namespace("Antares".into());
88        assert_eq!(TypeName::qualified(&ns, "Deal").0, "Antares.Deal");
89    }
90
91    #[test]
92    fn qualified_name_is_idempotent() {
93        let ns = Namespace("Antares".into());
94        assert_eq!(TypeName::qualified(&ns, "Antares.Deal").0, "Antares.Deal");
95    }
96}