Skip to main content

maplibre_native_ffi/
lib.rs

1//! Safe Rust binding for the MapLibre Native C API.
2//!
3//! This crate owns Rust-specific ergonomics and safety policy: thread-affine
4//! public handles, parent retention, owner-thread `Drop`, Rust errors,
5//! callback closure APIs, and lifetime-scoped render resources. Shared C ABI
6//! adaptation lives in `maplibre-native-ffi-core`.
7
8#![deny(unsafe_op_in_unsafe_fn)]
9
10mod camera;
11mod custom_geometry;
12mod events;
13mod geojson;
14mod handle;
15mod logging;
16mod map;
17mod options;
18mod projection;
19mod render;
20mod resource;
21mod runtime;
22mod values;
23
24use crate::values::NativeValue;
25use maplibre_native_ffi_core as maplibre_core;
26use maplibre_native_ffi_sys as sys;
27
28pub use camera::{
29    AnimationOptions, BoundOptions, BoundsConstraint, CameraFitOptions, CameraOptions,
30    FreeCameraOptions, ProjectionMode,
31};
32pub use custom_geometry::{CanonicalTileId, CustomGeometrySourceOptions};
33pub use events::{
34    CameraTransitionFinishedEvent, MapId, OfflineOperationCompletedEvent,
35    OfflineRegionResponseErrorEvent, OfflineRegionStatus, OfflineRegionStatusEvent,
36    OfflineRegionTileCountLimitEvent, RenderFrameEvent, RenderMapEvent, RenderingStats,
37    RuntimeEvent, RuntimeEventBatch, RuntimeEventPayload, RuntimeEventRef, RuntimeEventSource,
38    TileActionEvent, TileId, UnknownRuntimeEventPayload,
39};
40pub use geojson::GeoJsonSourceDataHandle;
41pub use logging::{LogRecord, clear_log_callback, set_async_log_severity_mask, set_log_callback};
42pub use map::{
43    GeoJsonSourceOptions, ImageContent, ImageStretch, LocationIndicatorImageKind, MapAttachRef,
44    MapHandle, RasterDemEncoding, SourceInfo, SourceType, StyleImage, StyleImageInfo,
45    StyleImageOptions, StyleImageTextFit, StyleLayerVisibility, StyleTransitionOptions,
46    TileJsonInfo, TileScheme, TileSourceOptions, VectorTileEncoding,
47};
48pub use maplibre_core::{
49    AmbientCacheOperation, CameraChangeMode, ConstrainMode, Error, ErrorKind, LogEvent,
50    LogSeverity, LogSeverityMask, MapDebugOptions, MapMode, MapOptions, MapTileOptions,
51    MapViewportOptions, NetworkStatus, NorthOrientation, OfflineOperationKind,
52    OfflineOperationResultKind, OfflineRegionDownloadState, OpenGLClientApi,
53    OpenGLContextOwnership, OpenGLContextProviderMask, RenderBackendMask, RenderMode, RenderResult,
54    ResourceErrorReason, ResourceKind, ResourceLoadingMethod, ResourcePriority,
55    ResourceResponseStatus, ResourceStoragePolicy, ResourceUsage, Result, RuntimeEventMask,
56    RuntimeEventType, TileLodMode, TileOperation, ViewportMode,
57};
58pub use maplibre_native_ffi_core::handle::{NativeHandleLeak, set_leak_reporter};
59pub use projection::MapProjectionHandle;
60pub use render::{
61    DetachedRenderSessionHandle, EglContextDescriptor, FeatureStateSelector, FrameNativePointer,
62    FrameOpenGLTextureName, MetalBorrowedTextureDescriptor, MetalContextDescriptor,
63    MetalOwnedTextureDescriptor, MetalOwnedTextureFrame, MetalOwnedTextureFrameHandle,
64    MetalSurfaceDescriptor, NativePointer, OpenGLBorrowedTextureDescriptor,
65    OpenGLContextDescriptor, OpenGLOwnedTextureDescriptor, OpenGLOwnedTextureFrame,
66    OpenGLOwnedTextureFrameHandle, OpenGLSurfaceDescriptor, PremultipliedRgba8Image,
67    QueriedFeature, RenderSessionHandle, RenderTargetExtent, RenderUpdate,
68    RenderedFeatureQueryOptions, RenderedQueryGeometry, SourceFeatureQueryOptions,
69    TextureImageInfo, VulkanBorrowedTextureDescriptor, VulkanContextDescriptor,
70    VulkanOwnedTextureDescriptor, VulkanOwnedTextureFrame, VulkanOwnedTextureFrameHandle,
71    VulkanSurfaceDescriptor, WebGlContextDescriptor, WebGpuBorrowedTextureDescriptor,
72    WebGpuContextDescriptor, WebGpuOwnedTextureDescriptor, WebGpuOwnedTextureFrame,
73    WebGpuOwnedTextureFrameHandle, WebGpuSurfaceDescriptor, WglContextDescriptor,
74};
75pub use resource::{
76    ByteRange, HttpHeader, HttpHeaderTransformRequest, ResourceProviderDecision, ResourceRequest,
77    ResourceRequestHandle, ResourceResponse, ResourceTransformRequest,
78};
79pub use runtime::{
80    OfflineOperationHandle, OfflineRegionDefinition, OfflineRegionInfo, RuntimeHandle,
81    RuntimeOptions, WakeSource,
82};
83pub use values::{
84    EdgeInsets, LatLng, LatLngBounds, ProjectedMeters, Quaternion, ScreenBox, ScreenPoint,
85    UnitBezier, Vec3,
86};
87
88/// Error returned by consuming one-shot handle operations when the handle
89/// remains live and the operation can be retried.
90#[derive(Debug)]
91pub struct HandleOperationError<T> {
92    error: Error,
93    handle: T,
94}
95
96impl<T> HandleOperationError<T> {
97    pub(crate) fn new(error: Error, handle: T) -> Self {
98        Self { error, handle }
99    }
100
101    /// Returns the operation error.
102    pub fn error(&self) -> &Error {
103        &self.error
104    }
105
106    /// Returns the stable category for the operation error.
107    pub fn kind(&self) -> ErrorKind {
108        self.error.kind()
109    }
110
111    /// Returns the raw C status for native operation errors, when available.
112    pub fn raw_status(&self) -> Option<i32> {
113        self.error.raw_status()
114    }
115
116    /// Returns the copied diagnostic message for the operation error.
117    pub fn diagnostic(&self) -> &str {
118        self.error.diagnostic()
119    }
120
121    /// Returns the operation error, dropping the still-live handle.
122    pub fn into_error(self) -> Error {
123        self.error
124    }
125
126    /// Returns the still-live handle so the operation can be retried.
127    pub fn into_handle(self) -> T {
128        self.handle
129    }
130
131    /// Splits this error into the operation error and still-live handle.
132    pub fn into_parts(self) -> (Error, T) {
133        (self.error, self.handle)
134    }
135}
136
137impl<T> std::fmt::Display for HandleOperationError<T> {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        self.error.fmt(f)
140    }
141}
142
143impl<T: std::fmt::Debug> std::error::Error for HandleOperationError<T> {}
144
145/// Error returned by offline operation result transfers.
146#[derive(Debug)]
147pub enum OfflineOperationTakeError<T> {
148    /// The native transfer failed before consuming the operation result.
149    Retryable(HandleOperationError<T>),
150    /// The native result was consumed, but copying it into Rust-owned data failed.
151    Consumed(Error),
152}
153
154impl<T> OfflineOperationTakeError<T> {
155    pub(crate) fn retryable(error: Error, handle: T) -> Self {
156        Self::Retryable(HandleOperationError::new(error, handle))
157    }
158
159    pub(crate) fn consumed(error: Error) -> Self {
160        Self::Consumed(error)
161    }
162
163    /// Returns the operation error.
164    pub fn error(&self) -> &Error {
165        match self {
166            Self::Retryable(error) => error.error(),
167            Self::Consumed(error) => error,
168        }
169    }
170
171    /// Returns the stable category for the operation error.
172    pub fn kind(&self) -> ErrorKind {
173        self.error().kind()
174    }
175
176    /// Returns the raw C status for native operation errors, when available.
177    pub fn raw_status(&self) -> Option<i32> {
178        self.error().raw_status()
179    }
180
181    /// Returns the copied diagnostic message for the operation error.
182    pub fn diagnostic(&self) -> &str {
183        self.error().diagnostic()
184    }
185
186    /// Returns the retryable error and still-live handle, if the operation was not consumed.
187    pub fn into_retryable(self) -> Option<HandleOperationError<T>> {
188        match self {
189            Self::Retryable(error) => Some(error),
190            Self::Consumed(_) => None,
191        }
192    }
193
194    /// Returns the operation error, dropping any retryable handle.
195    pub fn into_error(self) -> Error {
196        match self {
197            Self::Retryable(error) => error.into_error(),
198            Self::Consumed(error) => error,
199        }
200    }
201}
202
203impl<T> std::fmt::Display for OfflineOperationTakeError<T> {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        self.error().fmt(f)
206    }
207}
208
209impl<T: std::fmt::Debug> std::error::Error for OfflineOperationTakeError<T> {}
210
211/// Returns the native C ABI contract version.
212pub fn c_version() -> u32 {
213    // SAFETY: mln_c_version takes no arguments and returns the process-global C
214    // ABI version for the linked native library.
215    unsafe { sys::mln_c_version() }
216}
217
218/// Returns the render backends compiled into the linked native library.
219pub fn supported_render_backends() -> RenderBackendMask {
220    // SAFETY: mln_supported_render_backend_mask takes no arguments and returns
221    // a value mask.
222    let mask = unsafe { sys::mln_supported_render_backend_mask() };
223    RenderBackendMask::from_bits_retain(mask)
224}
225
226/// Returns the OpenGL context providers compiled into the linked native library.
227pub fn supported_opengl_context_providers() -> OpenGLContextProviderMask {
228    // SAFETY: mln_opengl_supported_context_provider_mask takes no arguments and
229    // returns a value mask.
230    let mask = unsafe { sys::mln_opengl_supported_context_provider_mask() };
231    OpenGLContextProviderMask::from_bits_retain(mask)
232}
233
234/// Converts a geographic coordinate to Spherical Mercator projected meters.
235pub fn projected_meters_for_lat_lng(coordinate: LatLng) -> Result<ProjectedMeters> {
236    let mut raw_meters = sys::mln_projected_meters {
237        northing: 0.0,
238        easting: 0.0,
239    };
240    // SAFETY: coordinate is passed by value. out_meters points to valid
241    // writable storage for one projected-meter value.
242    maplibre_core::check(unsafe {
243        sys::mln_projected_meters_for_lat_lng(coordinate.to_native(), &mut raw_meters)
244    })?;
245    Ok(ProjectedMeters::from_native(raw_meters))
246}
247
248/// Converts Spherical Mercator projected meters to a geographic coordinate.
249pub fn lat_lng_for_projected_meters(meters: ProjectedMeters) -> Result<LatLng> {
250    let mut raw_coordinate = sys::mln_lat_lng {
251        latitude: 0.0,
252        longitude: 0.0,
253    };
254    // SAFETY: meters is passed by value. out_coordinate points to valid
255    // writable storage for one coordinate value.
256    maplibre_core::check(unsafe {
257        sys::mln_lat_lng_for_projected_meters(meters.to_native(), &mut raw_coordinate)
258    })?;
259    Ok(LatLng::from_native(raw_coordinate))
260}
261
262/// Reads MapLibre Native's process-global network status.
263pub fn network_status() -> Result<NetworkStatus> {
264    maplibre_core::network_status()
265}
266
267/// Sets MapLibre Native's process-global network status.
268pub fn set_network_status(status: NetworkStatus) -> Result<()> {
269    maplibre_core::set_network_status(status)
270}
271
272#[cfg(test)]
273fn set_network_status_raw(raw_status: u32) -> Result<()> {
274    maplibre_core::set_network_status_raw(raw_status)
275}
276
277#[cfg(test)]
278mod tests {
279    use static_assertions::{assert_impl_all, assert_not_impl_any};
280
281    use super::*;
282
283    assert_not_impl_any!(RuntimeHandle: Send, Sync);
284    assert_not_impl_any!(MapHandle: Send, Sync);
285    assert_not_impl_any!(MapProjectionHandle: Send, Sync);
286    assert_not_impl_any!(NativePointer: Send, Sync);
287    assert_not_impl_any!(FrameNativePointer<'static>: Send, Sync);
288    assert_not_impl_any!(RenderSessionHandle: Send, Sync);
289    assert_not_impl_any!(DetachedRenderSessionHandle: Send, Sync);
290    // The one map value that crosses threads: a copied handle id that lets a
291    // render thread name a map owned elsewhere.
292    assert_impl_all!(MapAttachRef: Send, Sync);
293
294    #[test]
295    // Spec coverage: BND-103.
296    fn projected_meter_helpers_round_trip() {
297        let coordinate = LatLng::new(45.0, -122.0);
298        let meters = projected_meters_for_lat_lng(coordinate).unwrap();
299        let round_tripped = lat_lng_for_projected_meters(meters).unwrap();
300
301        assert!((round_tripped.latitude - coordinate.latitude).abs() < 1e-9);
302        assert!((round_tripped.longitude - coordinate.longitude).abs() < 1e-9);
303    }
304
305    #[test]
306    // Spec coverage: BND-020.
307    fn invalid_network_status_reports_public_error() {
308        let error = set_network_status_raw(999_999).unwrap_err();
309
310        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
311        assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
312        assert!(error.diagnostic().contains("network status"));
313    }
314
315    #[test]
316    // Spec coverage: BND-025 and BND-068.
317    fn unknown_network_status_is_rejected_before_calling_c() {
318        let error = set_network_status(NetworkStatus::Unknown(999_999)).unwrap_err();
319
320        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
321        assert_eq!(error.raw_status(), None);
322        assert!(error.diagnostic().contains("cannot be set"));
323    }
324}