relay_knowledge/domain/core/
source.rs1use serde::{Deserialize, Serialize};
2
3use super::{DomainError, error::required_text};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct SourceScope(String);
8
9impl SourceScope {
10 pub fn parse(value: impl Into<String>) -> Result<Self, DomainError> {
12 let scope = required_text("source_scope", value)?;
13 if scope.contains('\0') {
14 return Err(DomainError::invalid(
15 "source_scope",
16 "must not contain NUL bytes",
17 ));
18 }
19
20 Ok(Self(scope))
21 }
22
23 pub fn as_str(&self) -> &str {
25 &self.0
26 }
27}
28
29impl From<SourceScope> for String {
30 fn from(scope: SourceScope) -> Self {
31 scope.0
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 #[test]
40 fn trims_and_preserves_source_scope() {
41 let scope = SourceScope::parse(" docs/specs ").expect("scope should parse");
42
43 assert_eq!(scope.as_str(), "docs/specs");
44 }
45
46 #[test]
47 fn rejects_empty_source_scope() {
48 let error = SourceScope::parse(" ").expect_err("empty scope should fail");
49
50 assert_eq!(error.field, "source_scope");
51 }
52
53 #[test]
54 fn rejects_nul_bytes_and_converts_to_string() {
55 let error = SourceScope::parse("repo\0branch").expect_err("NUL should fail");
56 let scope: String = SourceScope::parse("repo")
57 .expect("scope should parse")
58 .into();
59
60 assert_eq!(error.field, "source_scope");
61 assert_eq!(scope, "repo");
62 }
63}