wire/carried_name.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! Names of things this crate does not own, on their way through it.
7//!
8//! A provider, a template, a toolkit slug: each arrives as text and
9//! leaves as a type that cannot be empty and cannot carry a control
10//! character. That is the whole of what this crate may judge.
11//!
12//! **The legal value set stays upstream, and only where it is open.**
13//! `gateway` owns which providers exist, `city` owns the templates, a
14//! broker's directory owns the slugs; each of those grows without this
15//! crate hearing about it, so a closed list here would be a second
16//! authority that goes stale the moment either side adds one. An
17//! unknown value is therefore an error where the authority is, and it
18//! says so with the name in hand. A set that is *closed* does not
19//! belong here at all: a run's mode is [`kernel::model::Mode`], which
20//! refuses an unknown word at this boundary rather than carrying it
21//! inward to be guessed at.
22//!
23//! One macro rather than three hand-written newtypes: they differ in
24//! their name and their doc line and in nothing else, and writing the
25//! same constructor three times is three places for it to drift.
26
27use kernel::{AxCode, AxError};
28use serde::{Deserialize, Serialize};
29
30macro_rules! carried_name {
31 ($name:ident, $doc:literal) => {
32 #[doc = $doc]
33 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
34 #[serde(transparent)]
35 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
36 pub struct $name(String);
37
38 impl $name {
39 /// Sole constructor. Rejects empty and control characters only:
40 /// the legal value set belongs to the upstream authority, and a
41 /// second copy of it here would be a second authority.
42 pub fn parse(raw: &str) -> Result<Self, AxError> {
43 if raw.is_empty() || raw.chars().any(char::is_control) {
44 return Err(AxError::failure(
45 AxCode::WireMismatch,
46 concat!("read ", stringify!($name), " from a frame"),
47 "the field is empty or holds control characters",
48 )
49 .with_recovery("send a non-empty single-line value"));
50 }
51 Ok(Self(raw.to_owned()))
52 }
53
54 /// The carried text. Whether it names something that exists is
55 /// answered upstream, not here.
56 #[must_use]
57 pub fn as_str(&self) -> &str {
58 &self.0
59 }
60 }
61 };
62}
63
64carried_name!(
65 ProviderName,
66 "A provider name in transit. Authority for the provider set is `gateway`."
67);
68carried_name!(
69 TemplateName,
70 "A Building template name in transit. Authority is `city`."
71);
72carried_name!(
73 ToolkitSlug,
74 "An outside application's id at the broker that connects it. Authority for which ids exist is the broker's directory, and percent-encoding it into a request path belongs to whoever builds that path."
75);
76#[cfg(test)]
77#[allow(clippy::unwrap_used, clippy::expect_used, reason = "test code")]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn a_carried_name_rejects_empty_and_control_characters() {
83 assert!(ProviderName::parse("").is_err());
84 assert!(ProviderName::parse("openai\nsteal").is_err());
85 assert_eq!(ProviderName::parse("openai").unwrap().as_str(), "openai");
86 // No closed list: a provider this build never heard of is the
87 // gateway's to refuse, not this boundary's.
88 assert!(ProviderName::parse("a-relay-we-have-never-heard-of").is_ok());
89 }
90}