Skip to main content

embassy_github/
types.rs

1// Copyright 2024-2026 Reflective Labs
2// SPDX-License-Identifier: MIT
3
4//! Minimal typed domain — placeholder shapes for the skeleton.
5//!
6//! Expand when an app pulls. The headline identifier is locked in so
7//! downstream consumers can reference the canonical type without
8//! committing to a richer entity shape they don't need yet.
9
10use serde::{Deserialize, Serialize};
11
12use crate::error::GithubError;
13
14/// The canonical identifier for this port's domain.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct OrgSlug(String);
18
19impl OrgSlug {
20    pub fn parse(raw: impl AsRef<str>) -> Result<Self, GithubError> {
21        let raw = raw.as_ref().trim();
22        if raw.is_empty() {
23            return Err(GithubError::InvalidIdentifier("empty".into()));
24        }
25        Ok(Self(raw.to_string()))
26    }
27
28    #[must_use]
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34/// Placeholder typed entity. Replace with real per-service fields when
35/// an app needs them; the `login` + `html_url` pair is
36/// intentionally minimal so the surface compiles without committing
37/// to a fuller schema.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct Organization {
40    pub login: OrgSlug,
41    pub html_url: String,
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn identifier_rejects_empty() {
50        // Intent: every port rejects empty IDs at the boundary —
51        // catching this here keeps garbage out of the audit log.
52        assert!(OrgSlug::parse("").is_err());
53        assert!(OrgSlug::parse("   ").is_err());
54    }
55
56    #[test]
57    fn entity_serde_round_trips() {
58        // Intent: payloads ride serde across the kernel boundary;
59        // a regression would block Formation composition.
60        let e = Organization {
61            login: OrgSlug::parse("STUB-001").unwrap(),
62            html_url: "Stub Entity".into(),
63        };
64        let json = serde_json::to_string(&e).unwrap();
65        let back: Organization = serde_json::from_str(&json).unwrap();
66        assert_eq!(back, e);
67    }
68}