Skip to content

feat!(sessions): newtype session ID, refactor store index - #275

Merged
twitchyliquid64 merged 1 commit into
mainfrom
tom/scaffolding
Jun 1, 2026
Merged

feat!(sessions): newtype session ID, refactor store index#275
twitchyliquid64 merged 1 commit into
mainfrom
tom/scaffolding

Conversation

@twitchyliquid64

@twitchyliquid64 twitchyliquid64 commented Jun 1, 2026

Copy link
Copy Markdown
Member

Implements some followups from #265, most notably a newtype for session IDs.

Summary by CodeRabbit

  • Refactor
    • Session identifiers unified to a domain-specific SessionId across RPC, session manager, SFTP, and storage for stronger typing and consistent behavior.
  • Bug Fixes
    • Improved session creation, lookup, and SFTP handling with clearer failure logging and more robust ID resolution.
  • Tests
    • Updated tests to cover the new SessionId behavior, including creation, listing, lookup, and persistence.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75498c2e-e762-4ddb-9941-78fd0799d40b

📥 Commits

Reviewing files that changed from the base of the PR and between ea874c6 and 08d0019.

📒 Files selected for processing (6)
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/store.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/minimald/src/sessions.rs
  • crates/sessions/src/lib.rs
  • crates/minimald/src/rpc.rs
  • crates/sessions/src/store.rs

📝 Walkthrough

Walkthrough

Introduces a SessionId newtype and replaces raw Uuid session identifiers with SessionId across the sessions store API and disk loader, sessions manager, exported RPC schema, SFTP subsystem, test harness, and unit tests.

Changes

SessionId Type Refactoring

Layer / File(s) Summary
SessionId type definition and Record model
crates/sessions/src/lib.rs
Defines SessionId newtype around Uuid with nil() and parse_str(), AsRef<Uuid> and Display impls, and updates Record.id to SessionId with serde default SessionId::nil().
Store trait interface refactoring
crates/sessions/src/store.rs
Changes SessionKey accessor to id() -> &SessionId, renames Loader::list()keys(), and find_by_uuid(&Uuid)find_by_id(&SessionId).
DiskSessionKey and Index implementation
crates/sessions/src/store.rs
Refactors DiskSessionKey to hold session_id: SessionId; rewrites the on-disk Index to map short/name → SessionId; updates DiskLoader::create() to generate/overwrite record.id with SessionId, check collisions via the new index, update/flush index, and return a DiskSessionKey with session_id.
Store unit tests
crates/sessions/src/store.rs
Updates tests to use SessionId::nil() and to assert find_by_id, create() overwrites, keys() yields SessionId, and persisted sessions are found by SessionId equality.
Sessions manager integration
crates/minimald/src/sessions.rs
Threads SessionId through manager/public API: SessionInfo.id and SessionKeyPredicate::Id use SessionId; manager lookups use find_by_id() and keys(); create returns k.id(); ManagerHandle::create_session returns Result<SessionId, ResponseError>.
RPC schema and test client updates
crates/minimald/src/rpc.rs, crates/minimald/src/test_harness.rs
RPC types ListSessionsEntry.id, GetSessionRecordRequest::Id, and CreateSessionResponse.id now use SessionId; tests use SessionId::nil(); TestClient::open_sftp() accepts SessionId.
SFTP subsystem and session parsing
crates/minimald/src/sftp.rs
handle_sftp_subsystem parses MINIMAL_SESSION_ID with SessionId::parse_str() and looks up sessions with SessionKeyPredicate::Id(session_id); SFTP test harness fresh_session() returns SessionId.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • gominimal/minimal#265: Both PRs touch the same session-creation RPC/manager surfaces and update created session ID types to SessionId.
  • gominimal/minimal#253: Introduced the initial Uuid-based sessions scaffolding that this PR refactors to SessionId.
  • gominimal/minimal#267: SFTP subsystem changes that overlap with this PR's SFTP SessionId wiring.

Suggested reviewers

  • evanspearman
  • norrietaylor
  • 0chroma

"A rabbit wrote a tiny patch to cheer,
SessionIds hop in, tidy and clear,
Tests updated, stores align,
RPC and SFTP now speak the same line,
Hooray — a small hop, no fear! 🐇"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: introducing a newtype for session IDs and refactoring the store index, which are the core modifications across all changed files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/sessions/src/lib.rs (1)

33-37: ⚡ Quick win

Inherent to_string shadows Display trait method.

The inherent to_string() returns the raw UUID string (self.0.to_string()), but Display formats as SessionId(uuid). This inconsistency triggers Clippy's inherent_to_string_shadow_display lint and may confuse callers expecting consistent behavior between format!("{}", id) and id.to_string().

Consider removing the inherent method and relying on Display, or aligning both to return the same format.

Option A: Remove inherent method, use Display for plain UUID
-    /// Constructs a string representing the given UUID.
-    #[must_use]
-    pub fn to_string(&self) -> String {
-        self.0.to_string()
-    }
 }
 
 impl AsRef<Uuid> for SessionId {
     fn as_ref(&self) -> &Uuid {
         &self.0
     }
 }
 
 impl fmt::Display for SessionId {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "SessionId({})", self.0)
+        write!(f, "{}", self.0)
     }
 }
Option B: Keep both, rename inherent method
-    /// Constructs a string representing the given UUID.
+    /// Returns the underlying UUID as a hyphenated string.
     #[must_use]
-    pub fn to_string(&self) -> String {
+    pub fn as_uuid_string(&self) -> String {
         self.0.to_string()
     }

Also applies to: 46-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sessions/src/lib.rs` around lines 33 - 37, The inherent pub fn
to_string(&self) on SessionId shadows the Display trait's to_string and triggers
clippy; fix by removing or renaming it so behavior is consistent—either delete
the inherent to_string and rely on the Display impl for string conversion, or
rename the inherent method (e.g., as_uuid or to_uuid_string) and update all call
sites; ensure the Display impl for SessionId remains unchanged and adjust the
other similar method at lines 46-50 the same way to avoid duplicate shadowing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/sessions/src/lib.rs`:
- Around line 33-37: The inherent pub fn to_string(&self) on SessionId shadows
the Display trait's to_string and triggers clippy; fix by removing or renaming
it so behavior is consistent—either delete the inherent to_string and rely on
the Display impl for string conversion, or rename the inherent method (e.g.,
as_uuid or to_uuid_string) and update all call sites; ensure the Display impl
for SessionId remains unchanged and adjust the other similar method at lines
46-50 the same way to avoid duplicate shadowing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a3758de-065a-45ae-b687-5d766602275a

📥 Commits

Reviewing files that changed from the base of the PR and between 882ff29 and af1026e.

📒 Files selected for processing (6)
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/store.rs

Comment thread crates/sessions/src/lib.rs Outdated

/// Constructs a string representing the given UUID.
#[must_use]
pub fn to_string(&self) -> String {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: when you implement Display, ToString is implemented automatically (and is actually the preferred way to implement ToString over manually doing a impl ToString for MyType.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made Display be a transparent impl of UUID's display, and removed to_string()

@twitchyliquid64
twitchyliquid64 enabled auto-merge (rebase) June 1, 2026 17:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/sessions/src/lib.rs (1)

20-20: ⚡ Quick win

Consider deriving Hash alongside Eq.

The SessionId type derives Eq but not Hash. It's a Rust best practice to derive Hash whenever Eq is derived to maintain the invariant that equal values must have equal hashes. Adding Hash also future-proofs the type for use in HashMap or HashSet as keys.

♻️ Add Hash to the derive list
-#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct SessionId(Uuid);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sessions/src/lib.rs` at line 20, The SessionId struct currently
derives Eq but not Hash; update its derive annotation (the line with
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd,
Ord)]) to also include Hash so SessionId implements std::hash::Hash and can be
used reliably as a key in HashMap/HashSet while preserving the Eq/Hash
invariant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/sessions/src/lib.rs`:
- Line 20: The SessionId struct currently derives Eq but not Hash; update its
derive annotation (the line with #[derive(Debug, Clone, Copy, Serialize,
Deserialize, PartialEq, Eq, PartialOrd, Ord)]) to also include Hash so SessionId
implements std::hash::Hash and can be used reliably as a key in HashMap/HashSet
while preserving the Eq/Hash invariant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8936dba2-6ef3-4658-86eb-fa7061bc3f56

📥 Commits

Reviewing files that changed from the base of the PR and between af1026e and 0bf5f85.

📒 Files selected for processing (6)
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/store.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/minimald/src/test_harness.rs
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/sessions/src/store.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
crates/sessions/src/store.rs (2)

266-271: ⚡ Quick win

Variable name uuid should be id for consistency.

The pattern variable is named uuid but the type is &SessionId, which is inconsistent with the refactoring's naming conventions used elsewhere in this file.

✨ Suggested fix
     fn find_by_name<S: AsRef<str>>(&self, name: S) -> Result<Option<Self::Key>, std::io::Error> {
-        match self.index.find_by_name(name) {
-            Some(uuid) => self.find_by_id(uuid),
+        match self.index.find_by_name(name) {
+            Some(id) => self.find_by_id(id),
             None => Ok(None),
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sessions/src/store.rs` around lines 266 - 271, The match binding in
find_by_name uses the variable name `uuid` while the type is &SessionId and
other code uses `id`; rename the pattern variable to `id` to match the
refactoring conventions: update the match arm from `Some(uuid) =>
self.find_by_id(uuid)` to `Some(id) => self.find_by_id(id)` and adjust any
related local uses in the find_by_name implementation (symbols: find_by_name,
self.index.find_by_name, find_by_id, SessionId, Self::Key).

139-145: 💤 Low value

Consider a reverse index for O(1) ID-to-short lookups.

short_by_id performs a linear scan through short_to_id, making find_by_id O(n) in the number of sessions. For typical session counts this is fine, but if session volume grows, a reverse BTreeMap<SessionId, String> (id_to_short) maintained alongside short_to_id would provide O(log n) lookups.

This is acceptable as-is for now given expected usage patterns.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sessions/src/store.rs` around lines 139 - 145, short_by_id currently
scans self.short_to_id linearly making lookups O(n); add a reverse index (e.g.,
a BTreeMap<SessionId, String> named id_to_short) and change short_by_id to
consult id_to_short for O(log n) lookups. Maintain id_to_short wherever
short_to_id is mutated: on insert/update populate
id_to_short.insert(session_id.clone(), short.clone()), and on remove delete
id_to_short.remove(session_id). Update any constructors, insert/remove helpers,
and tests that touch short_to_id to keep both maps consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/sessions/src/store.rs`:
- Around line 266-271: The match binding in find_by_name uses the variable name
`uuid` while the type is &SessionId and other code uses `id`; rename the pattern
variable to `id` to match the refactoring conventions: update the match arm from
`Some(uuid) => self.find_by_id(uuid)` to `Some(id) => self.find_by_id(id)` and
adjust any related local uses in the find_by_name implementation (symbols:
find_by_name, self.index.find_by_name, find_by_id, SessionId, Self::Key).
- Around line 139-145: short_by_id currently scans self.short_to_id linearly
making lookups O(n); add a reverse index (e.g., a BTreeMap<SessionId, String>
named id_to_short) and change short_by_id to consult id_to_short for O(log n)
lookups. Maintain id_to_short wherever short_to_id is mutated: on insert/update
populate id_to_short.insert(session_id.clone(), short.clone()), and on remove
delete id_to_short.remove(session_id). Update any constructors, insert/remove
helpers, and tests that touch short_to_id to keep both maps consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8202a674-96df-48fc-b72a-10a3a6ccac29

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf5f85 and ea874c6.

📒 Files selected for processing (6)
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/store.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/test_harness.rs
  • crates/sessions/src/lib.rs
  • crates/minimald/src/sessions.rs

@twitchyliquid64
twitchyliquid64 merged commit d70f502 into main Jun 1, 2026
8 checks passed
@twitchyliquid64
twitchyliquid64 deleted the tom/scaffolding branch June 1, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants