Skip to content

Commit 3616890

Browse files
refactor: unify token types under NatsToken in trogon-nats
1 parent 035824c commit 3616890

11 files changed

Lines changed: 346 additions & 80 deletions

File tree

rsworkspace/crates/acp-nats/src/acp_prefix.rs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
//! malformed dots (consecutive, leading, trailing). Max 128 bytes. Validity is guaranteed at
88
//! construction.
99
10-
use std::sync::Arc;
11-
12-
use crate::nats::token;
10+
use crate::nats_token_policies::MultiTokenPolicy;
1311
use crate::subject_token_violation::SubjectTokenViolation;
14-
15-
const MAX_PREFIX_LENGTH: usize = 128;
12+
use trogon_nats::NatsToken;
1613

1714
/// Error returned when [`AcpPrefix`] validation fails.
1815
#[derive(Debug, Clone, PartialEq)]
@@ -36,28 +33,16 @@ impl std::error::Error for AcpPrefixError {}
3633

3734
/// NATS-safe ACP prefix. Guarantees validity at construction—invalid instances are unrepresentable.
3835
#[derive(Clone)]
39-
pub struct AcpPrefix(Arc<str>);
36+
pub struct AcpPrefix(NatsToken<MultiTokenPolicy>);
4037

4138
impl AcpPrefix {
4239
pub fn new(s: impl Into<String>) -> Result<Self, AcpPrefixError> {
4340
let s = s.into();
44-
if s.is_empty() {
45-
return Err(AcpPrefixError(SubjectTokenViolation::Empty));
46-
}
47-
if let Some(ch) = token::has_wildcards_or_whitespace(&s) {
48-
return Err(AcpPrefixError(SubjectTokenViolation::InvalidCharacter(ch)));
49-
}
50-
if token::has_consecutive_or_boundary_dots(&s) {
51-
return Err(AcpPrefixError(SubjectTokenViolation::InvalidCharacter('.')));
52-
}
53-
if s.len() > MAX_PREFIX_LENGTH {
54-
return Err(AcpPrefixError(SubjectTokenViolation::TooLong(s.len())));
55-
}
56-
Ok(Self(s.into()))
41+
NatsToken::new(s).map(Self).map_err(AcpPrefixError)
5742
}
5843

5944
pub fn as_str(&self) -> &str {
60-
&self.0
45+
self.0.as_str()
6146
}
6247
}
6348

rsworkspace/crates/acp-nats/src/ext_method_name.rs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@
55
//! rejects `*`, `>`, whitespace; allows dotted namespaces (e.g. `vendor.operation`) but rejects
66
//! malformed dots (consecutive, leading, trailing). Validity is guaranteed at construction.
77
8-
use std::sync::Arc;
9-
10-
use crate::nats::token;
8+
use crate::nats_token_policies::MultiTokenPolicy;
119
use crate::subject_token_violation::SubjectTokenViolation;
12-
13-
const MAX_METHOD_NAME_LENGTH: usize = 128;
10+
use trogon_nats::NatsToken;
1411

1512
/// Error returned when [`ExtMethodName`] validation fails.
1613
#[derive(Debug, Clone, PartialEq)]
@@ -36,32 +33,15 @@ impl std::error::Error for ExtMethodNameError {}
3633
///
3734
/// Rejects empty, too-long, wildcard, whitespace, and malformed dotted names.
3835
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39-
pub struct ExtMethodName(Arc<str>);
36+
pub struct ExtMethodName(NatsToken<MultiTokenPolicy>);
4037

4138
impl ExtMethodName {
4239
pub fn new(method: impl AsRef<str>) -> Result<Self, ExtMethodNameError> {
43-
let s = method.as_ref();
44-
if s.is_empty() {
45-
return Err(ExtMethodNameError(SubjectTokenViolation::Empty));
46-
}
47-
if s.len() > MAX_METHOD_NAME_LENGTH {
48-
return Err(ExtMethodNameError(SubjectTokenViolation::TooLong(s.len())));
49-
}
50-
if let Some(ch) = token::has_wildcards_or_whitespace(s) {
51-
return Err(ExtMethodNameError(SubjectTokenViolation::InvalidCharacter(
52-
ch,
53-
)));
54-
}
55-
if token::has_consecutive_or_boundary_dots(s) {
56-
return Err(ExtMethodNameError(SubjectTokenViolation::InvalidCharacter(
57-
'.',
58-
)));
59-
}
60-
Ok(Self(s.into()))
40+
NatsToken::new(method).map(Self).map_err(ExtMethodNameError)
6141
}
6242

6343
pub fn as_str(&self) -> &str {
64-
&self.0
44+
self.0.as_str()
6545
}
6646
}
6747

rsworkspace/crates/acp-nats/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(crate) mod ext_method_name;
77
pub(crate) mod in_flight_slot_guard;
88
pub(crate) mod jsonrpc;
99
pub mod nats;
10+
pub(crate) mod nats_token_policies;
1011
pub(crate) mod pending_prompt_waiters;
1112
pub mod session_id;
1213
pub mod subject_token_violation;

rsworkspace/crates/acp-nats/src/nats/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
mod extensions;
22
pub mod parsing;
33
mod subjects;
4-
pub(crate) mod token;
54

65
pub use extensions::ExtSessionReady;
76
pub use parsing::{ClientMethod, ParsedClientSubject, parse_client_subject};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use trogon_nats::NatsTokenPolicy;
2+
3+
/// Single NATS subject token: no dots, ASCII-only, max 128 chars.
4+
///
5+
/// Used for values embedded as a single token in a subject, e.g. session IDs.
6+
pub struct SingleTokenPolicy;
7+
8+
impl NatsTokenPolicy for SingleTokenPolicy {
9+
const ALLOW_DOTS: bool = false;
10+
const REQUIRE_ASCII: bool = true;
11+
const MAX_LENGTH: usize = 128;
12+
}
13+
14+
/// Multi-token NATS subject segment: dots allowed as separators, max 128 bytes.
15+
///
16+
/// Used for values that may contain dotted namespaces, e.g. prefixes and method names.
17+
pub struct MultiTokenPolicy;
18+
19+
impl NatsTokenPolicy for MultiTokenPolicy {
20+
const ALLOW_DOTS: bool = true;
21+
const REQUIRE_ASCII: bool = false;
22+
const MAX_LENGTH: usize = 128;
23+
}

rsworkspace/crates/acp-nats/src/session_id.rs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
//! Validation follows [NATS subject naming](https://docs.nats.io/nats-concepts/subjects#characters-allowed-and-recommended-for-subject-names):
55
//! ASCII only (recommended), rejecting `.` `*` `>` and whitespace (forbidden). Validity is
66
//! guaranteed at construction.
7-
//!
8-
//! TODO: Consider extracting to `trogon-nats` as a generic `NatsSubject` (or `NatsToken`) type
9-
//! so prefix, session_id, and other subject tokens share the same validation.
107
8+
use crate::nats_token_policies::SingleTokenPolicy;
119
use crate::subject_token_violation::SubjectTokenViolation;
12-
13-
const MAX_SESSION_ID_LENGTH: usize = 128;
10+
use trogon_nats::NatsToken;
1411

1512
/// Error returned when [`AcpSessionId`] validation fails.
1613
#[derive(Debug, Clone, PartialEq)]
@@ -37,32 +34,15 @@ impl std::error::Error for SessionIdError {}
3734
/// Follows [NATS subject naming](https://docs.nats.io/nats-concepts/subjects#characters-allowed-and-recommended-for-subject-names):
3835
/// ASCII only; rejects `.`, `*`, `>`, and whitespace. Max 128 characters.
3936
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40-
pub struct AcpSessionId(std::sync::Arc<str>);
37+
pub struct AcpSessionId(NatsToken<SingleTokenPolicy>);
4138

4239
impl AcpSessionId {
4340
pub fn new(s: impl AsRef<str>) -> Result<Self, SessionIdError> {
44-
let s = s.as_ref();
45-
if s.is_empty() {
46-
return Err(SessionIdError(SubjectTokenViolation::Empty));
47-
}
48-
let mut char_count = 0;
49-
for ch in s.chars() {
50-
char_count += 1;
51-
if char_count > MAX_SESSION_ID_LENGTH {
52-
return Err(SessionIdError(SubjectTokenViolation::TooLong(char_count)));
53-
}
54-
if !ch.is_ascii() {
55-
return Err(SessionIdError(SubjectTokenViolation::InvalidCharacter(ch)));
56-
}
57-
if ch == '.' || ch == '*' || ch == '>' || ch.is_whitespace() {
58-
return Err(SessionIdError(SubjectTokenViolation::InvalidCharacter(ch)));
59-
}
60-
}
61-
Ok(Self(s.into()))
41+
NatsToken::new(s).map(Self).map_err(SessionIdError)
6242
}
6343

6444
pub fn as_str(&self) -> &str {
65-
&self.0
45+
self.0.as_str()
6646
}
6747
}
6848

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
/// Describes what went wrong when validating a NATS subject token: empty, invalid character, or too long.
2-
#[derive(Debug, Clone, PartialEq)]
3-
pub enum SubjectTokenViolation {
4-
Empty,
5-
InvalidCharacter(char),
6-
TooLong(usize),
7-
}
1+
pub use trogon_nats::SubjectTokenViolation;

rsworkspace/crates/trogon-nats/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub mod auth;
4040
pub mod client;
4141
pub mod connect;
4242
pub mod messaging;
43+
pub mod nats_token;
44+
pub mod subject_token_violation;
45+
pub mod token;
4346

4447
#[cfg(feature = "test-support")]
4548
pub mod mocks;
@@ -53,6 +56,8 @@ pub use messaging::{
5356
RetryPolicy, headers_with_trace_context, inject_trace_context, publish, request,
5457
request_with_timeout,
5558
};
59+
pub use nats_token::{NatsToken, NatsTokenPolicy};
60+
pub use subject_token_violation::SubjectTokenViolation;
5661

5762
#[cfg(feature = "test-support")]
5863
pub use mocks::{AdvancedMockNatsClient, MockNatsClient};

0 commit comments

Comments
 (0)