odgi_ffi/lib.rs
1// src/lib.rs
2//! A safe Rust interface to the `odgi` C++ library.
3//!
4//! The `odgi-ffi` crate provides high-level, idiomatic Rust bindings for querying
5//! [ODGI](https://github.com/pangenome/odgi) graphs. It handles the complexity of the
6//! C++ FFI boundary, providing a safe and easy-to-use API for Rust developers.
7//!
8//! The primary entry point is the [`Graph`] struct, which represents a loaded ODGI graph
9//! in memory. This crate also provides utility functions for converting between GFA and
10//! ODGI file formats.
11//!
12//! # Modules
13//!
14//! - [`graph`]: Contains the main [`Graph`] struct for querying graph data.
15//! - [`conversion`]: Provides functions like [`gfa_to_odgi`] for format conversion.
16//!
17//! # Features
18//!
19//! - Load ODGI graphs from disk into a safe Rust wrapper.
20//! - Query graph properties, such as node count, path names, and node sequences.
21//! - Perform topological queries, such as finding node successors and predecessors.
22//! - Project path coordinates to their corresponding nodes and offsets.
23//! - Convert between GFA and ODGI formats using the bundled `odgi` executable.
24//!
25//! # Example
26//!
27//! Here's a complete example of loading a graph and performing some basic queries.
28//!
29//! ```rust,no_run
30//! use odgi_ffi::{Graph, gfa_to_odgi};
31//! use tempfile::NamedTempFile;
32//! use std::io::Write;
33//!
34//! // Create a temporary GFA file for the example.
35//! let mut gfa_file = NamedTempFile::new().unwrap();
36//! writeln!(gfa_file, "H\tVN:Z:1.0").unwrap();
37//! writeln!(gfa_file, "S\t1\tGATTACA").unwrap();
38//! writeln!(gfa_file, "S\t2\tT").unwrap();
39//! writeln!(gfa_file, "L\t1\t+\t2\t+\t0M").unwrap();
40//! writeln!(gfa_file, "P\tx\t1+,2+\t*").unwrap();
41//! let gfa_path = gfa_file.path();
42//!
43//! // Create a path for the ODGI output file.
44//! let odgi_file = NamedTempFile::new().unwrap();
45//! let odgi_path = odgi_file.path();
46//!
47//! // 1. Convert the GFA file to an ODGI file.
48//! // This function is only available when not using the `docs-only` feature.
49//! # #[cfg(not(feature = "docs-only"))]
50//! gfa_to_odgi(gfa_path.to_str().unwrap(), odgi_path.to_str().unwrap())
51//! .expect("Failed to convert GFA to ODGI");
52//!
53//! // 2. Load the ODGI graph into memory.
54//! let graph = Graph::load(odgi_path.to_str().unwrap())
55//! .expect("Failed to load ODGI graph");
56//!
57//! // 3. Query the graph.
58//! assert_eq!(graph.node_count(), 2);
59//!
60//! let path_names = graph.get_path_names();
61//! assert_eq!(path_names, vec!["x"]);
62//!
63//! let seq = graph.get_node_sequence(1);
64//! assert_eq!(seq, "GATTACA");
65//!
66//! // Get path length using the new method.
67//! let length = graph.get_path_length("x").unwrap();
68//! assert_eq!(length, 8);
69//!
70//! // Projecting position 7 on path "x" should land at the start of node 2.
71//! let position = graph.project("x", 7).unwrap();
72//! assert_eq!(position.node_id, 2);
73//! assert_eq!(position.offset, 0);
74//! ```
75
76mod graph;
77
78// Conditionally compile the conversion module.
79// It will not exist for docs.rs builds.
80#[cfg(not(feature = "docs-only"))]
81mod conversion;
82
83// Publicly re-export the core types for easy access.
84pub use graph::{Graph, Error, Edge, PathPosition};
85
86// Conditionally re-export the conversion functions.
87#[cfg(not(feature = "docs-only"))]
88pub use conversion::{gfa_to_odgi, odgi_to_gfa};
89
90
91// --- REAL FFI BRIDGE (for normal builds) ---
92#[cfg(not(feature = "docs-only"))]
93#[cxx::bridge(namespace = "odgi")]
94mod ffi {
95 /// Represents a directed edge between two nodes in the graph.
96 #[derive(Debug, Clone)]
97 struct Edge {
98 /// The ID of the node this edge points to.
99 to_node: u64,
100 /// The orientation of the "from" node's handle in this edge.
101 from_orientation: bool,
102 /// The orientation of the "to" node's handle in this edge.
103 to_orientation: bool,
104 }
105
106 /// Represents a specific position on a path.
107 #[derive(Debug, Clone)]
108 struct PathPosition {
109 /// The ID of the node at this position.
110 node_id: u64,
111 /// The 0-based offset within the node's sequence.
112 offset: u64,
113 /// The orientation of the node on the path at this position.
114 is_forward: bool,
115 }
116
117 unsafe extern "C++" {
118 include!("odgi-ffi/src/odgi_wrapper.hpp");
119 include!("odgi-ffi/src/lib.rs.h");
120
121 type graph_t;
122 #[namespace = ""]
123 type OpaqueGraph;
124
125 #[namespace = ""]
126 fn load_graph(path: &str) -> UniquePtr<OpaqueGraph>;
127 #[namespace = ""]
128 fn get_graph_t<'a>(graph: &'a OpaqueGraph) -> &'a graph_t;
129 #[namespace = ""]
130 fn get_node_count(graph: &graph_t) -> u64;
131 #[namespace = ""]
132 fn graph_get_path_names(graph: &graph_t) -> Vec<String>;
133 #[namespace = ""]
134 fn graph_project(graph: &graph_t, path_name: &str, pos: u64) -> UniquePtr<PathPosition>;
135 #[namespace = ""]
136 fn graph_get_node_sequence(graph: &graph_t, node_id: u64) -> String;
137 #[namespace = ""]
138 fn graph_get_node_len(graph: &graph_t, node_id: u64) -> u64;
139 #[namespace = ""]
140 fn graph_get_successors(graph: &graph_t, node_id: u64) -> Vec<Edge>;
141 #[namespace = ""]
142 fn graph_get_predecessors(graph: &graph_t, node_id: u64) -> Vec<Edge>;
143 #[namespace = ""]
144 fn graph_get_paths_on_node(graph: &graph_t, node_id: u64) -> Vec<String>;
145 #[namespace = ""]
146 fn graph_get_path_length(graph: &graph_t, path_name: &str) -> u64;
147 #[namespace = ""]
148 fn graph_get_next_node_on_path(graph: &graph_t, path_name: &str, node_id: u64) -> i64;
149 #[namespace = ""]
150 fn graph_get_paths_on_edge(
151 graph: &graph_t,
152 from_node: u64,
153 from_orient: bool,
154 to_node: u64,
155 to_orient: bool
156 ) -> Vec<String>;
157 }
158}
159
160// --- MOCK FFI BRIDGE (for docs.rs) ---
161#[cfg(feature = "docs-only")]
162mod ffi {
163 // This self-contained mock module provides all the types that `graph.rs` needs
164 // to compile its public API for documentation purposes.
165
166 // Mock the opaque C++ types.
167 pub enum OpaqueGraph {}
168
169 // Provide mock definitions for the shared structs.
170 #[derive(Debug, Clone)]
171 pub struct Edge {
172 pub to_node: u64,
173 pub from_orientation: bool,
174 pub to_orientation: bool,
175 }
176
177 #[derive(Debug, Clone)]
178 pub struct PathPosition {
179 pub node_id: u64,
180 pub offset: u64,
181 pub is_forward: bool,
182 }
183}