You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

76 lines
3.0 KiB
Markdown

+++
title = "Add graph computation module (src/graph.rs)"
priority = 5
status = "done"
ticket_type = "feature"
dependencies = []
+++
Implement `src/graph.rs` — a module that builds a directed dependency graph from a flat list of tickets and provides the data structures needed by the ASCII renderer and JSON output.
## Motivation
The `nbd graph` command (see CLI ticket) needs to traverse `Ticket.dependencies` edges to produce an ordered, tree-structured representation of the dependency DAG. This module isolates that logic from I/O and rendering.
## Data structures
```rust
/// A node in the dependency graph.
pub struct GraphNode<'a> {
pub ticket: &'a Ticket,
/// Direct dependents (tickets that list this ticket as a dependency).
pub dependents: Vec<&'a str>,
/// Direct dependencies (tickets this ticket depends on).
pub dependencies: Vec<&'a str>,
}
/// A directed dependency graph built from a flat list of tickets.
pub struct TicketGraph<'a> {
nodes: IndexMap<&'a str, GraphNode<'a>>,
}
```
## Functions to implement
### `TicketGraph::build(tickets: &[Ticket]) -> TicketGraph`
- Constructs `nodes` map keyed by ticket ID.
- Iterates `ticket.dependencies` to populate both `dependencies` (forward) and `dependents` (reverse) edges.
- IDs in `dependencies` that do not correspond to a known ticket are silently ignored (dangling references are tolerated).
### `TicketGraph::roots(&self) -> Vec<&Ticket>`
- Returns tickets with no dependencies (or whose dependencies are all outside the graph).
- Sorted by priority descending (same ordering as `list_tickets`).
- These become the starting points for the recursive ASCII tree renderer.
### `TicketGraph::subtree(&self, root_id: &str) -> Vec<&str>`
- Returns all ticket IDs reachable from `root_id` by following `dependencies` edges in depth-first order, including `root_id` itself.
- Cycles: track visited set; stop recursion when an ID has been visited. This makes the function safe even if the data contains cycles.
### `TicketGraph::to_json_value(&self) -> serde_json::Value`
- Returns an object like:
```json
{
"nodes": [
{"id": "a3f9c2", "title": "...", "status": "todo", "dependencies": ["b7d41e"]},
...
],
"edges": [
{"from": "a3f9c2", "to": "b7d41e"},
...
]
}
```
## Crate dependencies
No new crates needed. If `IndexMap` insertion-order is useful, `indexmap` can be added — but a `HashMap` with a separate sorted `Vec<&str>` of keys also works. Prefer whatever avoids adding a new crate dependency.
## Files touched
- `src/graph.rs` — new file, public module
- `src/main.rs``mod graph;` declaration
## Tests (unit, in `src/tests.rs`)
- `build` with an empty slice returns an empty graph.
- `roots` returns only tickets with no in-graph dependencies.
- `subtree` returns the correct set of IDs for a linear chain.
- `subtree` does not infinite-loop when the data contains a cycle.
- `to_json_value` contains all expected IDs in `nodes` and all edges in `edges`.