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