Skip to content

Commit 84355c0

Browse files
refactor: unify token types under NatsToken in trogon-nats (#44)
Signed-off-by: itsitsiridakis <iosif.tsitsiridakis@fanatics.live> Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent ec91507 commit 84355c0

11 files changed

Lines changed: 306 additions & 78 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,11 +7,8 @@
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::constants::MAX_PREFIX_LENGTH;
13-
use crate::nats::token;
14-
use crate::subject_token_violation::SubjectTokenViolation;
10+
use trogon_nats::SubjectTokenViolation;
11+
use trogon_nats::DottedNatsToken;
1512

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

3633
/// NATS-safe ACP prefix. Guarantees validity at construction—invalid instances are unrepresentable.
3734
#[derive(Clone, Debug)]
38-
pub struct AcpPrefix(Arc<str>);
35+
pub struct AcpPrefix(DottedNatsToken);
3936

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

5843
pub fn as_str(&self) -> &str {
59-
&self.0
44+
self.0.as_str()
6045
}
6146
}
6247

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ pub const SESSION_READY_DELAY: Duration = Duration::from_millis(100);
1919
pub const PROMPT_TIMEOUT_WARNING_SUPPRESSION_WINDOW: Duration = Duration::from_secs(5);
2020
pub const TEST_PROMPT_TIMEOUT: Duration = Duration::from_secs(5);
2121

22-
pub const MAX_PREFIX_LENGTH: usize = 128;
23-
pub const MAX_SESSION_ID_LENGTH: usize = 128;
24-
pub const MAX_METHOD_NAME_LENGTH: usize = 128;
25-
2622
pub const AGENT_UNAVAILABLE: i32 = -32001;
2723

2824
pub const SESSION_PREFIX: &str = ".session.";

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

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
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::constants::MAX_METHOD_NAME_LENGTH;
11-
use crate::nats::token;
12-
use crate::subject_token_violation::SubjectTokenViolation;
8+
use trogon_nats::SubjectTokenViolation;
9+
use trogon_nats::DottedNatsToken;
1310

1411
/// Error returned when [`ExtMethodName`] validation fails.
1512
#[derive(Debug, Clone, PartialEq)]
@@ -35,32 +32,17 @@ impl std::error::Error for ExtMethodNameError {}
3532
///
3633
/// Rejects empty, too-long, wildcard, whitespace, and malformed dotted names.
3734
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
38-
pub struct ExtMethodName(Arc<str>);
35+
pub struct ExtMethodName(DottedNatsToken);
3936

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

6244
pub fn as_str(&self) -> &str {
63-
&self.0
45+
self.0.as_str()
6446
}
6547
}
6648

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ pub(crate) mod jsonrpc;
1212
pub mod nats;
1313
pub(crate) mod pending_prompt_waiters;
1414
pub mod session_id;
15-
pub mod subject_token_violation;
1615
pub(crate) mod telemetry;
1716

1817
pub use acp_prefix::{AcpPrefix, AcpPrefixError};

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
use serde::Serialize;
76
use serde::de::DeserializeOwned;

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

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@
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
11-
use crate::constants::MAX_SESSION_ID_LENGTH;
12-
use crate::subject_token_violation::SubjectTokenViolation;
8+
use trogon_nats::SubjectTokenViolation;
9+
use trogon_nats::NatsToken;
1310

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

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

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
1212
pub const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(30);
1313

1414
pub const REQ_ID_HEADER: &str = "X-Req-Id";
15+
16+
pub const MAX_NATS_TOKEN_LENGTH: usize = 128;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub mod connect;
4242
pub mod constants;
4343
pub mod jetstream;
4444
pub mod messaging;
45+
pub mod nats_token;
46+
pub mod subject_token_violation;
47+
pub(crate) mod token;
4548

4649
#[cfg(feature = "test-support")]
4750
pub mod mocks;
@@ -56,6 +59,8 @@ pub use messaging::{
5659
RetryPolicy, build_request_headers, headers_with_trace_context, inject_trace_context, publish,
5760
request, request_with_timeout,
5861
};
62+
pub use nats_token::{DottedNatsToken, NatsToken};
63+
pub use subject_token_violation::SubjectTokenViolation;
5964

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

0 commit comments

Comments
 (0)