[M2/M4] mxaccess-rpc: Guid::parse_str + dedupe examples (resolves F17)

Adds `Guid::parse_str(&str) -> Result<Guid, RpcError>` to
`crates/mxaccess-rpc/src/guid.rs` as the inverse of the existing
`Display` impl. Accepts the canonical dashed-hex form, optionally
braced (.NET `B` format), case-insensitive, and tolerant of bare
32-char hex without dashes. Single-pass char-by-char nibble accumulator
avoids per-byte string allocation; applies the same byte-swap of
groups 1-3 that the `Display` impl reads.

Eight new tests cover round-trip against the existing `Display`
fixture (`crates/mxaccess-rpc/src/guid.rs:111-119`,
`b49f92f7-c748-4169-8eca-a0670b012746`), braces, uppercase, no-dashes,
zero-GUID, too-short, too-long, and non-hex rejection.

The five live-NMX examples (`connect-write-read`, `subscribe`,
`recovery`, `multi-tag`, `secured-write`) lose their per-file 15-line
`parse_guid` helpers in favour of the canonical implementation.
`asb-subscribe` and `subscribe-buffered` are unaffected — they don't
parse GUIDs.

Test count delta: 524 → 532 (+8)
Open followups touched: F17 resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 10:18:21 -04:00
parent af939730b1
commit 48d3a9d6da
7 changed files with 149 additions and 111 deletions
@@ -80,8 +80,7 @@ impl LiveEnv {
}
let host = std::env::var("MX_NMX_HOST")?;
let addr = parse_host_port(&host, 135)?;
let ipid_str = std::env::var("MX_NMX_SERVICE_IPID")?;
let service_ipid = parse_guid(&ipid_str)?;
let service_ipid = Guid::parse_str(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let tag = std::env::var("MX_TEST_TAG").unwrap_or_else(|_| "TestChildObject.TestInt".into());
Ok(Some(Self {
addr,
@@ -110,29 +109,6 @@ fn parse_host_port(
)
}
/// Parse a `12345678-1234-1234-1234-123456789012` style GUID into wire-byte
/// form. The first three groups are stored little-endian on the wire (per
/// `mxaccess_rpc::guid::Guid` module docstring); groups 4 and 5 are stored
/// verbatim. Tested against the Display round-trip in `guid.rs`.
fn parse_guid(s: &str) -> Result<Guid, Box<dyn std::error::Error>> {
let trimmed = s.trim_start_matches('{').trim_end_matches('}');
let hex: String = trimmed.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return Err(format!("invalid GUID format: {s}").into());
}
let mut bytes = [0u8; 16];
for (i, chunk) in bytes.iter_mut().enumerate() {
let pair = hex
.get(i * 2..i * 2 + 2)
.ok_or("guid hex slice out of range")?;
*chunk = u8::from_str_radix(pair, 16)?;
}
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Ok(Guid(bytes))
}
// ---- canned in-memory resolver ----------------------------------------------
struct StaticResolver {
+1 -20
View File
@@ -112,7 +112,7 @@ impl LiveEnv {
}
let host = std::env::var("MX_NMX_HOST")?;
let addr = parse_host_port(&host, 135)?;
let service_ipid = parse_guid(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let service_ipid = Guid::parse_str(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let tags = std::env::var("MX_TEST_TAGS")
.unwrap_or_else(|_| "TestChildObject.TestInt,TestChildObject.TestBool".into())
.split(',')
@@ -146,25 +146,6 @@ fn parse_host_port(
)
}
fn parse_guid(s: &str) -> Result<Guid, Box<dyn std::error::Error>> {
let trimmed = s.trim_start_matches('{').trim_end_matches('}');
let hex: String = trimmed.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return Err(format!("invalid GUID format: {s}").into());
}
let mut bytes = [0u8; 16];
for (i, chunk) in bytes.iter_mut().enumerate() {
let pair = hex
.get(i * 2..i * 2 + 2)
.ok_or("guid hex slice out of range")?;
*chunk = u8::from_str_radix(pair, 16)?;
}
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Ok(Guid(bytes))
}
/// Multi-tag canned resolver. Returns Int32 metadata for any tag whose
/// `Object.Attribute` form matches the configured allow-list, with a
/// fresh `attribute_id` per tag so AdviseSupervisory frames don't
+1 -20
View File
@@ -103,7 +103,7 @@ impl LiveEnv {
}
let host = std::env::var("MX_NMX_HOST")?;
let addr = parse_host_port(&host, 135)?;
let service_ipid = parse_guid(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let service_ipid = Guid::parse_str(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let tag = std::env::var("MX_TEST_TAG").unwrap_or_else(|_| "TestChildObject.TestInt".into());
Ok(Some(Self {
addr,
@@ -132,25 +132,6 @@ fn parse_host_port(
)
}
fn parse_guid(s: &str) -> Result<Guid, Box<dyn std::error::Error>> {
let trimmed = s.trim_start_matches('{').trim_end_matches('}');
let hex: String = trimmed.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return Err(format!("invalid GUID format: {s}").into());
}
let mut bytes = [0u8; 16];
for (i, chunk) in bytes.iter_mut().enumerate() {
let pair = hex
.get(i * 2..i * 2 + 2)
.ok_or("guid hex slice out of range")?;
*chunk = u8::from_str_radix(pair, 16)?;
}
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Ok(Guid(bytes))
}
struct StaticResolver {
tag_reference: String,
metadata: GalaxyTagMetadata,
+1 -20
View File
@@ -115,7 +115,7 @@ impl LiveEnv {
}
let host = std::env::var("MX_NMX_HOST")?;
let addr = parse_host_port(&host, 135)?;
let service_ipid = parse_guid(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let service_ipid = Guid::parse_str(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let tag = std::env::var("MX_TEST_TAG")
.unwrap_or_else(|_| "TestChildObject.TestSecuredInt".into());
let user_id: i32 = std::env::var("MX_TEST_USER_ID")?.parse()?;
@@ -152,25 +152,6 @@ fn parse_host_port(
)
}
fn parse_guid(s: &str) -> Result<Guid, Box<dyn std::error::Error>> {
let trimmed = s.trim_start_matches('{').trim_end_matches('}');
let hex: String = trimmed.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return Err(format!("invalid GUID format: {s}").into());
}
let mut bytes = [0u8; 16];
for (i, chunk) in bytes.iter_mut().enumerate() {
let pair = hex
.get(i * 2..i * 2 + 2)
.ok_or("guid hex slice out of range")?;
*chunk = u8::from_str_radix(pair, 16)?;
}
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Ok(Guid(bytes))
}
struct StaticResolver {
tag_reference: String,
metadata: GalaxyTagMetadata,
+1 -20
View File
@@ -83,7 +83,7 @@ impl LiveEnv {
}
let host = std::env::var("MX_NMX_HOST")?;
let addr = parse_host_port(&host, 135)?;
let service_ipid = parse_guid(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let service_ipid = Guid::parse_str(&std::env::var("MX_NMX_SERVICE_IPID")?)?;
let tag = std::env::var("MX_TEST_TAG").unwrap_or_else(|_| "TestChildObject.TestInt".into());
Ok(Some(Self {
addr,
@@ -112,25 +112,6 @@ fn parse_host_port(
)
}
fn parse_guid(s: &str) -> Result<Guid, Box<dyn std::error::Error>> {
let trimmed = s.trim_start_matches('{').trim_end_matches('}');
let hex: String = trimmed.chars().filter(|c| *c != '-').collect();
if hex.len() != 32 {
return Err(format!("invalid GUID format: {s}").into());
}
let mut bytes = [0u8; 16];
for (i, chunk) in bytes.iter_mut().enumerate() {
let pair = hex
.get(i * 2..i * 2 + 2)
.ok_or("guid hex slice out of range")?;
*chunk = u8::from_str_radix(pair, 16)?;
}
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Ok(Guid(bytes))
}
struct StaticResolver {
tag_reference: String,
metadata: GalaxyTagMetadata,