feat(#760): Wire SysExIdentity into PortResolver (#752)#778
feat(#760): Wire SysExIdentity into PortResolver (#752)#778amiable-dev merged 4 commits intomainfrom
Conversation
Extend PortInfo with sysex_identity and update PortResolver to check matches_with_sysex() alongside matches_with_usb(). SysExIdentity matchers now resolve ports when identity data is provided. - Add sysex_identity: Option<SysExIdentity> to PortInfo - Resolver chains: matches_with_usb() || matches_with_sysex() - Constructors (new, new_with_usb) default sysex_identity to None - TDD: 2 new tests (SysEx match, no-match without metadata) 16 resolver tests pass (14 existing + 2 new). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Wires SysEx Identity metadata into the PortResolver pipeline so DeviceMatcher::SysExIdentity matchers can participate in runtime port binding when identity reply data is available.
Changes:
- Extend
PortInfowithsysex_identity: Option<SysExIdentity>(defaulting toNonein constructors). - Update
PortResolver::resolve()to treat a matcher as matched whenmatches_with_usb(...) || matches_with_sysex(...)is true. - Add resolver tests covering SysExIdentity match with metadata and no-match without metadata.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| conductor-core/src/resolver.rs | Adds SysEx identity metadata to PortInfo and uses it during resolution via matches_with_sysex() |
| conductor-core/tests/resolver_test.rs | Adds TDD coverage to validate SysExIdentity-based binding behavior |
| @@ -17,16 +18,19 @@ pub struct PortInfo { | |||
| pub vendor_id: Option<u16>, | |||
| /// USB Product ID (populated when available from platform APIs) | |||
| pub product_id: Option<u16>, | |||
| /// SysEx Identity Reply data (populated by probing, when available) | |||
| pub sysex_identity: Option<SysExIdentity>, | |||
| } | |||
There was a problem hiding this comment.
PortInfo is a public struct with public fields; adding sysex_identity is a semver-breaking change for any downstream crate that constructs PortInfo via a struct literal (it must now provide the new field). If this type is part of the public API, consider making PortInfo #[non_exhaustive] and/or making fields private and exposing constructor/builder APIs so future metadata additions don’t keep causing breaking changes.
| for matcher in &identity.matchers { | ||
| if matcher.matches_with_usb(&port.name, port.vendor_id, port.product_id) { | ||
| let matched = | ||
| matcher.matches_with_usb(&port.name, port.vendor_id, port.product_id) | ||
| || matcher.matches_with_sysex(&port.name, port.sysex_identity.as_ref()); | ||
| if matched { |
There was a problem hiding this comment.
Now that PortResolver::resolve() invokes matches_with_sysex(), any existing documentation that says SysEx identity metadata is “not yet passed to the resolver” becomes stale/misleading. Please update the relevant doc comments (e.g., on DeviceMatcher::matches_with_sysex) to reflect the new wiring so future readers don’t miss that SysExIdentity matchers are active in resolution.
- Doc: matches_with_sysex() is now called by PortResolver (not stale) - Add doc note recommending constructors over struct literals for PortInfo Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
conductor-core/src/resolver.rs
Outdated
| let matched = | ||
| matcher.matches_with_usb(&port.name, port.vendor_id, port.product_id) | ||
| || matcher.matches_with_sysex(&port.name, port.sysex_identity.as_ref()); | ||
| if matched { |
There was a problem hiding this comment.
resolve() now evaluates both matches_with_usb() and matches_with_sysex() for every matcher. Because matches_with_sysex() delegates to matches(port_name) for non-SysEx matchers, name-based matchers that don’t match will do the same name/regex check twice (and for regex this also means two mutex-lock + regex is_match paths). Consider only calling matches_with_sysex() when the matcher is DeviceMatcher::SysExIdentity { .. } (or otherwise avoid the fallback-to-name path) to prevent the extra work.
| let ports = vec![PortInfo { | ||
| name: "Generic MIDI Port".to_string(), | ||
| index: 0, | ||
| vendor_id: None, | ||
| product_id: None, | ||
| sysex_identity: Some(SysExIdentity { | ||
| manufacturer_id: vec![0x42], // KORG | ||
| family: 0x0034, | ||
| model: 0x0001, | ||
| version: [1, 0, 0, 0], | ||
| }), | ||
| }]; |
There was a problem hiding this comment.
This test constructs PortInfo via a struct literal, even though PortInfo’s docs now recommend using constructors to reduce future breakage when metadata fields are added. To keep tests consistent with that intent (and avoid having to set unrelated fields like VID/PID), consider building with PortInfo::new(...) and then setting sysex_identity on the instance.
| let ports = vec![PortInfo { | |
| name: "Generic MIDI Port".to_string(), | |
| index: 0, | |
| vendor_id: None, | |
| product_id: None, | |
| sysex_identity: Some(SysExIdentity { | |
| manufacturer_id: vec![0x42], // KORG | |
| family: 0x0034, | |
| model: 0x0001, | |
| version: [1, 0, 0, 0], | |
| }), | |
| }]; | |
| let mut port = PortInfo::new("Generic MIDI Port", 0); | |
| port.sysex_identity = Some(SysExIdentity { | |
| manufacturer_id: vec![0x42], // KORG | |
| family: 0x0034, | |
| model: 0x0001, | |
| version: [1, 0, 0, 0], | |
| }); | |
| let ports = vec![port]; |
- Only call matches_with_sysex() for SysExIdentity matchers; other matchers use matches_with_usb() which already handles name matching - Use PortInfo::new() + field set instead of struct literal in test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
conductor-core/src/identity.rs
Outdated
| /// Note: `PortResolver::resolve()` calls both `matches_with_usb()` and this method, | ||
| /// passing `port.sysex_identity` from `PortInfo`. SysExIdentity matchers are active | ||
| /// in binding resolution when identity data is available. |
There was a problem hiding this comment.
The doc comment says PortResolver::resolve() calls both matches_with_usb() and matches_with_sysex(), but the resolver currently selects one method based on matcher type (SysExIdentity -> matches_with_sysex, all others -> matches_with_usb). Please update this note to reflect the actual call pattern to avoid misleading future changes/debugging.
| /// Note: `PortResolver::resolve()` calls both `matches_with_usb()` and this method, | |
| /// passing `port.sysex_identity` from `PortInfo`. SysExIdentity matchers are active | |
| /// in binding resolution when identity data is available. | |
| /// Note: `PortResolver::resolve()` selects between `matches_with_usb()` and this | |
| /// method based on matcher type: `SysExIdentity` matchers use this method with | |
| /// `port.sysex_identity` from `PortInfo`, while all other matchers use | |
| /// `matches_with_usb()`. SysExIdentity matchers are only active in binding | |
| /// resolution when identity data is available. |
…h (round 3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Wire the existing
matches_with_sysex()method into the port resolution pipeline. SysExIdentity matchers now resolve ports when identity data is provided.Changes
sysex_identity: Option<SysExIdentity>toPortInfoPortResolver::resolve()chains:matches_with_usb() || matches_with_sysex()new,new_with_usb) defaultsysex_identitytoNoneTDD
test_sysex_identity_matches_with_metadata: KORG identity resolves to correct bindingtest_sysex_identity_no_match_without_metadata: no identity data → UnboundTest plan
cargo clippy --workspacecleancargo test --workspacePipeline wiring plan complete
All 3 wiring PRs:
🤖 Generated with Claude Code