odgi_ffi/
graph.rs

1// src/graph.rs
2
3//! Provides the main [`Graph`] struct for in-memory graph operations.
4//!
5//! This module defines the central [`Graph`] object, which is the primary
6//! entry point for querying a loaded ODGI graph. It also defines the
7//! associated [`Error`] type for handling failures.
8
9use cxx::UniquePtr;
10use std::error::Error as StdError;
11use std::fmt;
12use super::ffi;
13
14// Re-export the FFI data structures so they are part of the public API
15// and can be used as return types from the Graph methods.
16pub use super::ffi::{Edge, PathPosition};
17
18/// A custom error type for operations within the `odgi-ffi` crate.
19///
20/// This error is returned by functions that might fail, such as [`Graph::load`].
21#[derive(Debug)]
22pub struct Error(pub String);
23
24impl fmt::Display for Error {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        write!(f, "{}", self.0)
27    }
28}
29
30impl StdError for Error {}
31
32/// A safe, idiomatic Rust wrapper around a C++ `odgi::graph_t` object.
33///
34/// A `Graph` instance represents a pangenome graph loaded into memory.
35/// Once loaded, you can use its methods to perform various queries, such as
36/// retrieving node sequences, finding paths, and traversing the graph structure.
37///
38/// The only way to create a `Graph` is by calling [`Graph::load`].
39pub struct Graph {
40    // This field will only exist in real builds.
41    #[cfg(not(feature = "docs-only"))]
42    inner: UniquePtr<ffi::OpaqueGraph>,
43
44    // For docs builds, add a dummy field to make the struct valid.
45    #[cfg(feature = "docs-only")]
46    _inner: (),
47}
48
49// --- REAL IMPLEMENTATION (for normal builds) ---
50#[cfg(not(feature = "docs-only"))]
51impl Graph {
52    /// Loads an ODGI graph from a file into memory.
53    ///
54    /// # Arguments
55    ///
56    /// * `path` - A string slice that holds the path to the ODGI file.
57    ///
58    /// # Errors
59    ///
60    /// Returns an [`Error`] if the file does not exist or if the file format is invalid.
61    ///
62    /// # Examples
63    ///
64    /// ```rust,no_run
65    /// use odgi_ffi::Graph;
66    ///
67    /// match Graph::load("my_graph.odgi") {
68    ///     Ok(graph) => println!("Graph loaded successfully!"),
69    ///     Err(e) => eprintln!("Failed to load graph: {}", e),
70    /// }
71    /// ```
72    pub fn load(path: &str) -> Result<Self, Error> {
73        let graph_ptr = ffi::load_graph(path);
74        if graph_ptr.is_null() {
75            Err(Error(format!("Failed to load ODGI graph from '{}'", path)))
76        } else {
77            Ok(Graph { inner: graph_ptr })
78        }
79    }
80
81    /// Returns the total number of nodes in the graph.
82    ///
83    /// # Examples
84    ///
85    /// ```rust,no_run
86    /// # use odgi_ffi::Graph;
87    /// # let graph = Graph::load("my_graph.odgi").unwrap();
88    /// let count = graph.node_count();
89    /// println!("The graph has {} nodes.", count);
90    /// ```
91    pub fn node_count(&self) -> u64 {
92        let graph_t_ref = ffi::get_graph_t(&self.inner);
93        ffi::get_node_count(graph_t_ref)
94    }
95
96    /// Returns a list of all path names in the graph.
97    ///
98    /// # Examples
99    ///
100    /// ```rust,no_run
101    /// # use odgi_ffi::Graph;
102    /// # let graph = Graph::load("my_graph.odgi").unwrap();
103    /// let paths = graph.get_path_names();
104    /// for path_name in paths {
105    ///     println!("Found path: {}", path_name);
106    /// }
107    /// ```
108    pub fn get_path_names(&self) -> Vec<String> {
109        let graph_t_ref = ffi::get_graph_t(&self.inner);
110        ffi::graph_get_path_names(graph_t_ref)
111    }
112
113    /// Projects a 0-based linear coordinate on a path to graph coordinates.
114    ///
115    /// This is useful for finding which node and offset corresponds to a
116    /// specific position along a named path.
117    ///
118    /// # Arguments
119    ///
120    /// * `path_name` - The name of the path to project onto.
121    /// * `pos` - The 0-based nucleotide position along the path.
122    ///
123    /// # Returns
124    ///
125    /// Returns `Some(PathPosition)` if the path exists and the position is
126    /// within its bounds. Returns `None` otherwise.
127    ///
128    /// # Examples
129    ///
130    /// ```rust,no_run
131    /// # use odgi_ffi::Graph;
132    /// # let graph = Graph::load("my_graph.odgi").unwrap();
133    /// if let Some(position) = graph.project("human_chr1", 1_000_000) {
134    ///     println!("Position 1M on chr1 is at node {} offset {}",
135    ///              position.node_id, position.offset);
136    /// } else {
137    ///     println!("Position not found on path.");
138    /// }
139    /// ```
140    pub fn project(&self, path_name: &str, pos: u64) -> Option<PathPosition> {
141        let graph_t_ref = ffi::get_graph_t(&self.inner);
142        let result_ptr = ffi::graph_project(graph_t_ref, path_name, pos);
143
144        if result_ptr.is_null() {
145            None
146        } else {
147            Some(result_ptr.as_ref().unwrap().clone())
148        }
149    }
150
151    /// Gets the DNA sequence for a given node ID.
152    ///
153    /// # Arguments
154    ///
155    /// * `node_id` - The ID of the node to query.
156    ///
157    /// # Returns
158    ///
159    /// Returns the sequence as a `String`. If the `node_id` is invalid,
160    /// an empty string is returned.
161    pub fn get_node_sequence(&self, node_id: u64) -> String {
162        let graph_t_ref = ffi::get_graph_t(&self.inner);
163        ffi::graph_get_node_sequence(graph_t_ref, node_id)
164    }
165
166    /// Gets the length of the sequence for a given node ID.
167    ///
168    /// # Arguments
169    ///
170    /// * `node_id` - The ID of the node to query.
171    ///
172    /// # Returns
173    ///
174    /// Returns the sequence length. If the `node_id` is invalid, `0` is returned.
175    pub fn get_node_len(&self, node_id: u64) -> u64 {
176        let graph_t_ref = ffi::get_graph_t(&self.inner);
177        ffi::graph_get_node_len(graph_t_ref, node_id)
178    }
179
180    /// Gets all successor edges for a given node ID.
181    ///
182    /// Successors are the nodes immediately following this one in the graph topology.
183    pub fn get_successors(&self, node_id: u64) -> Vec<Edge> {
184        let graph_t_ref = ffi::get_graph_t(&self.inner);
185        ffi::graph_get_successors(graph_t_ref, node_id)
186    }
187
188    /// Gets all predecessor edges for a given node ID.
189    ///
190    /// Predecessors are the nodes immediately preceding this one in the graph topology.
191    pub fn get_predecessors(&self, node_id: u64) -> Vec<Edge> {
192        let graph_t_ref = ffi::get_graph_t(&self.inner);
193        ffi::graph_get_predecessors(graph_t_ref, node_id)
194    }
195
196    /// Gets the names of all paths that step on a given node ID.
197    pub fn get_paths_on_node(&self, node_id: u64) -> Vec<String> {
198        let graph_t_ref = ffi::get_graph_t(&self.inner);
199        ffi::graph_get_paths_on_node(graph_t_ref, node_id)
200    }
201
202    /// Gets the total length of a path in base pairs.
203    ///
204    /// # Arguments
205    ///
206    /// * `path_name` - The name of the path to measure.
207    ///
208    /// # Returns
209    ///
210    /// Returns `Some(u64)` with the path length if the path exists.
211    /// Returns `None` if no path with that name is found in the graph.
212    pub fn get_path_length(&self, path_name: &str) -> Option<u64> {
213        let graph_t_ref = ffi::get_graph_t(&self.inner);
214        // We can use the existing get_path_names to check for existence first,
215        // making our Rust API safer and more idiomatic than the C++ one.
216        if !self.get_path_names().iter().any(|p| p == path_name) {
217            return None;
218        }
219        let length = ffi::graph_get_path_length(graph_t_ref, path_name);
220        Some(length)
221    }
222    // ADD THIS NEW PUBLIC METHOD
223    /// Gets the next node ID on a given path from a specified node.
224    ///
225    /// # Returns
226    ///
227    /// Returns `Some(u64)` with the next node ID if the current node is on the
228    /// path and is not the last node. Returns `None` otherwise.
229    pub fn get_next_node_on_path(&self, node_id: u64, path_name: &str) -> Option<u64> {
230        let graph_t_ref = ffi::get_graph_t(&self.inner);
231        let next_node_id = ffi::graph_get_next_node_on_path(graph_t_ref, path_name, node_id);
232        if next_node_id >= 0 {
233            Some(next_node_id as u64)
234        } else {
235            None
236        }
237    }
238
239    /// Gets the names of all paths that traverse a specific directed edge.
240    ///
241    /// An edge is defined by a source node and a destination node, including the
242    /// orientation of each node. This function will return a unique list of
243    /// path names that step from `from_node` to `to_node` with the specified
244    /// orientations.
245    ///
246    /// # Arguments
247    ///
248    /// * `from_node` - The ID of the node where the edge begins.
249    /// * `from_orientation` - The orientation of the `from_node`. `true` for forward, `false` for reverse.
250    /// * `to_node` - The ID of the node where the edge ends.
251    /// * `to_orientation` - The orientation of the `to_node`. `true` for forward, `false` for reverse.
252    ///
253    /// # Returns
254    ///
255    /// Returns a `Vec<String>` containing the names of all paths on the given edge.
256    /// If the edge does not exist on any path, an empty vector is returned.
257    ///
258    /// # Examples
259    ///
260    /// ```rust,no_run
261    /// # use odgi_ffi::Graph;
262    /// # let graph = Graph::load("my_graph.odgi").unwrap();
263    /// // Find paths going from the forward orientation of node 1 to the forward orientation of node 2.
264    /// let paths = graph.get_paths_on_edge(1, true, 2, true);
265    /// for path_name in paths {
266    ///     println!("Found path on edge 1+ -> 2+: {}", path_name);
267    /// }
268    /// ```
269    pub fn get_paths_on_edge(
270        &self,
271        from_node: u64,
272        from_orientation: bool,
273        to_node: u64,
274        to_orientation: bool,
275    ) -> Vec<String> {
276        let graph_t_ref = ffi::get_graph_t(&self.inner);
277        ffi::graph_get_paths_on_edge(
278            graph_t_ref,
279            from_node,
280            from_orientation,
281            to_node,
282            to_orientation
283        )
284    }
285}
286
287// --- MOCK IMPLEMENTATION (for docs.rs) ---
288#[cfg(feature = "docs-only")]
289impl Graph {
290    /// Loads an ODGI graph from a file into memory.
291    pub fn load(_path: &str) -> Result<Self, Error> { Ok(Graph { _inner: () }) }
292
293    /// Returns the total number of nodes in the graph.
294    pub fn node_count(&self) -> u64 { 0 }
295
296    /// Returns a list of all path names in the graph.
297    pub fn get_path_names(&self) -> Vec<String> { vec![] }
298
299    /// Projects a 0-based linear coordinate on a path to graph coordinates.
300    pub fn project(&self, _path_name: &str, _pos: u64) -> Option<PathPosition> { None }
301
302    /// Gets the DNA sequence for a given node ID.
303    pub fn get_node_sequence(&self, _node_id: u64) -> String { String::new() }
304
305    /// Gets the length of the sequence for a given node ID.
306    pub fn get_node_len(&self, _node_id: u64) -> u64 { 0 }
307
308    /// Gets all successor edges for a given node ID.
309    pub fn get_successors(&self, _node_id: u64) -> Vec<Edge> { vec![] }
310
311    /// Gets all predecessor edges for a given node ID.
312    pub fn get_predecessors(&self, _node_id: u64) -> Vec<Edge> { vec![] }
313
314    /// Gets the names of all paths that step on a given node ID.
315    pub fn get_paths_on_node(&self, _node_id: u64) -> Vec<String> { vec![] }
316
317    /// Gets the total length of a path in base pairs.
318    pub fn get_path_length(&self, _path_name: &str) -> Option<u64> { None }
319
320    /// Gets the names of all paths that traverse a specific directed edge.
321    pub fn get_paths_on_edge(
322        &self,
323        _from_node: u64,
324        _from_orientation: bool,
325        _to_node: u64,
326        _to_orientation: bool,
327    ) -> Vec<String> {
328        vec![]
329    }
330}
331
332
333/// Marks the `Graph` struct as safe to send between threads.
334// The `unsafe` keyword is our guarantee to the compiler that we've ensured
335// the underlying C++ object is safe to be sent and accessed across threads,
336// which is true in our read-only use case.
337unsafe impl Send for Graph {}
338
339/// Marks the `Graph` struct as safe to share between threads.
340// The `unsafe` keyword is our guarantee to the compiler that we've ensured
341// the underlying C++ object is safe to be sent and accessed across threads,
342// which is true in our read-only use case.
343unsafe impl Sync for Graph {}