Skip to main content

oxigdal_mobile/
lib.rs

1//! OxiGDAL Mobile SDK - FFI bindings for iOS and Android
2//!
3//! This crate provides C-compatible FFI bindings that enable OxiGDAL to be used
4//! from iOS (Swift/Objective-C) and Android (Kotlin/Java) applications.
5//!
6//! # Architecture
7//!
8//! The mobile SDK is organized into several layers:
9//!
10//! - **FFI Layer** (`ffi` module): C-compatible types and functions
11//! - **Platform Layer** (`ios`/`android` modules): Platform-specific utilities
12//! - **Language Bindings** (Swift/Kotlin): High-level wrappers (in `bindings/` directory)
13//!
14//! # Safety
15//!
16//! While this crate uses `unsafe` extensively due to FFI requirements, it provides
17//! safety guarantees through:
18//!
19//! - Extensive null pointer validation
20//! - Bounds checking on all array operations
21//! - UTF-8 validation on string conversions
22//! - Proper resource lifecycle management
23//! - Thread-safe error handling
24//!
25//! # Memory Management
26//!
27//! The FFI layer follows these conventions:
28//!
29//! - **Handles**: Created by `*_open` or `*_create`, freed by `*_close` or `*_free`
30//! - **Strings**: Returned strings must be freed with `oxigdal_string_free`
31//! - **Buffers**: Caller-allocated, OxiGDAL only writes to them
32//! - **Opaque Types**: Must not be dereferenced on the foreign side
33//!
34//! # Error Handling
35//!
36//! All FFI functions return `OxiGdalErrorCode`:
37//! - `Success` (0) indicates success
38//! - Non-zero values indicate specific error types
39//! - Detailed messages available via `oxigdal_get_last_error()`
40//!
41//! # Example (C API)
42//!
43//! ```c
44//! // Initialize
45//! oxigdal_init();
46//!
47//! // Open dataset
48//! OxiGdalDataset* dataset;
49//! if (oxigdal_dataset_open("/path/to/file.tif", &dataset) != Success) {
50//!     char* error = oxigdal_get_last_error();
51//!     printf("Error: %s\n", error);
52//!     oxigdal_string_free(error);
53//!     return;
54//! }
55//!
56//! // Get metadata
57//! OxiGdalMetadata metadata;
58//! oxigdal_dataset_get_metadata(dataset, &metadata);
59//! printf("Size: %d x %d\n", metadata.width, metadata.height);
60//!
61//! // Read region
62//! OxiGdalBuffer* buffer = oxigdal_buffer_alloc(256, 256, 3);
63//! oxigdal_dataset_read_region(dataset, 0, 0, 256, 256, 1, buffer);
64//!
65//! // Cleanup
66//! oxigdal_buffer_free(buffer);
67//! oxigdal_dataset_close(dataset);
68//! oxigdal_cleanup();
69//! ```
70//!
71//! # Features
72//!
73//! - `std` (default): Enable standard library support
74//! - `ios`: Enable iOS-specific bindings
75//! - `android`: Enable Android JNI bindings
76//! - `offline`: Enable offline COG reading
77//! - `filters`: Enable image enhancement filters
78//! - `tiles`: Enable map tile generation
79//!
80//! # Platform Support
81//!
82//! ## iOS
83//! - Target: `aarch64-apple-ios`, `x86_64-apple-ios` (simulator)
84//! - Swift bindings available in `bindings/ios/`
85//! - Integration: CocoaPods, Swift Package Manager
86//!
87//! ## Android
88//! - Targets: `aarch64-linux-android`, `armv7-linux-androideabi`, `x86_64-linux-android`
89//! - Kotlin bindings available in `bindings/android/`
90//! - Integration: Gradle, AAR library
91//!
92//! # COOLJAPAN Policies
93//!
94//! This crate adheres to COOLJAPAN ecosystem policies:
95//! - **Pure Rust**: No C/C++ dependencies by default
96//! - **No Unwrap**: All error cases explicitly handled
97//! - **Workspace**: Version management via workspace
98//! - **Latest Crates**: Always use latest stable dependencies
99
100// FFI code requires unsafe functions, unsafe blocks, and no_mangle symbols
101#![allow(unsafe_code)]
102#![allow(clippy::missing_safety_doc)]
103#![allow(clippy::not_unsafe_ptr_arg_deref)]
104// FFI code requires no_mangle for C symbol export
105#![allow(clippy::no_mangle_with_rust_abi)]
106// FFI code uses expect() for internal invariant checks
107#![allow(clippy::expect_used)]
108// Allow unnecessary unsafe blocks (wrapped in outer unsafe fn)
109#![allow(unused_unsafe)]
110// Allow unused variables in FFI (may be used conditionally)
111#![allow(unused_variables)]
112// Allow unused imports in platform-specific code
113#![allow(unused_imports)]
114// Allow manual div_ceil for compatibility
115#![allow(clippy::manual_div_ceil)]
116// Allow complex types in FFI interfaces
117#![allow(clippy::type_complexity)]
118// Allow match collapsing warnings - explicit matches preferred in FFI
119#![allow(clippy::collapsible_match)]
120// Allow first element access with get(0)
121#![allow(clippy::get_first)]
122// Allow too many arguments for complex FFI operations
123#![allow(clippy::too_many_arguments)]
124#![warn(missing_docs)]
125#![deny(unsafe_op_in_unsafe_fn)]
126#![cfg_attr(not(feature = "std"), no_std)]
127#![allow(unsafe_attr_outside_unsafe)]
128// Allow unexpected cfg for conditional compilation
129#![allow(unexpected_cfgs)]
130
131#[cfg(feature = "alloc")]
132extern crate alloc;
133
134pub mod common;
135pub mod ffi;
136
137#[cfg(feature = "ios")]
138pub mod ios;
139
140#[cfg(feature = "android")]
141pub mod android;
142
143// Re-export main types for convenience
144pub use ffi::types::*;
145pub use ffi::{oxigdal_cleanup, oxigdal_init};
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_library_init() {
153        let result = oxigdal_init();
154        assert_eq!(result, OxiGdalErrorCode::Success);
155
156        let result = oxigdal_cleanup();
157        assert_eq!(result, OxiGdalErrorCode::Success);
158    }
159
160    #[test]
161    fn test_error_codes_are_unique() {
162        // Ensure error codes don't overlap
163        let codes = vec![
164            OxiGdalErrorCode::Success as i32,
165            OxiGdalErrorCode::NullPointer as i32,
166            OxiGdalErrorCode::InvalidArgument as i32,
167            OxiGdalErrorCode::FileNotFound as i32,
168            OxiGdalErrorCode::IoError as i32,
169            OxiGdalErrorCode::UnsupportedFormat as i32,
170            OxiGdalErrorCode::OutOfBounds as i32,
171            OxiGdalErrorCode::AllocationFailed as i32,
172            OxiGdalErrorCode::InvalidUtf8 as i32,
173            OxiGdalErrorCode::DriverError as i32,
174            OxiGdalErrorCode::ProjectionError as i32,
175            OxiGdalErrorCode::Unknown as i32,
176        ];
177
178        let mut unique_codes = codes.clone();
179        unique_codes.sort();
180        unique_codes.dedup();
181
182        assert_eq!(
183            codes.len(),
184            unique_codes.len(),
185            "Error codes must be unique"
186        );
187    }
188
189    #[test]
190    fn test_repr_c_sizes() {
191        use std::mem::size_of;
192
193        // Ensure FFI types are reasonably sized
194        assert!(size_of::<OxiGdalMetadata>() < 256);
195        assert!(size_of::<OxiGdalBbox>() == 32); // 4 * f64
196        assert!(size_of::<OxiGdalPoint>() == 24); // 3 * f64
197        assert!(size_of::<OxiGdalTileCoord>() == 12); // 3 * i32
198        assert!(size_of::<OxiGdalEnhanceParams>() == 32); // 4 * f64
199    }
200
201    #[test]
202    fn test_default_values() {
203        let enhance = OxiGdalEnhanceParams::default();
204        assert_eq!(enhance.brightness, 1.0);
205        assert_eq!(enhance.contrast, 1.0);
206        assert_eq!(enhance.saturation, 1.0);
207        assert_eq!(enhance.gamma, 1.0);
208
209        let resampling = OxiGdalResampling::default();
210        assert_eq!(resampling, OxiGdalResampling::Bilinear);
211    }
212}