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}