Skip to content

Commit 06cd35d

Browse files
bors[bot]frewsxcv
andauthored
Merge #98
98: Update `proj::Proj` constructors to return `Result` instead of `Option`. r=michaelkirk a=frewsxcv Surface the underlying error from PROJ. Co-authored-by: Corey Farwell <coreyf@rwell.org>
2 parents 83fca44 + 1467e28 commit 06cd35d

2 files changed

Lines changed: 98 additions & 22 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,6 @@ pub use crate::proj::Coord;
217217
pub use crate::proj::Info;
218218
pub use crate::proj::Proj;
219219
pub use crate::proj::ProjBuilder;
220+
pub use crate::proj::ProjCreateError;
220221
pub use crate::proj::ProjError;
221222
pub use crate::proj::Projinfo;

src/proj.rs

Lines changed: 97 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ use libc::{c_char, c_double};
33
use num_traits::Float;
44
use proj_sys::{
55
proj_area_create, proj_area_destroy, proj_area_set_bbox, proj_cleanup, proj_context_create,
6-
proj_context_destroy, proj_context_get_url_endpoint, proj_context_is_network_enabled,
7-
proj_context_set_search_paths, proj_context_set_url_endpoint, proj_create,
8-
proj_create_crs_to_crs, proj_destroy, proj_errno_string, proj_get_area_of_use,
6+
proj_context_destroy, proj_context_errno, proj_context_get_url_endpoint,
7+
proj_context_is_network_enabled, proj_context_set_search_paths, proj_context_set_url_endpoint,
8+
proj_create, proj_create_crs_to_crs, proj_destroy, proj_errno_string, proj_get_area_of_use,
99
proj_grid_cache_set_enable, proj_info, proj_normalize_for_visualization, proj_pj_info,
1010
proj_trans, proj_trans_array, PJconsts, PJ_AREA, PJ_CONTEXT, PJ_COORD, PJ_DIRECTION_PJ_FWD,
1111
PJ_DIRECTION_PJ_INV, PJ_INFO, PJ_LP, PJ_XY,
1212
};
13-
use std::fmt::{self, Debug};
13+
use std::{
14+
ffi,
15+
fmt::{self, Debug},
16+
str,
17+
};
1418

1519
#[cfg(feature = "network")]
1620
use proj_sys::proj_context_set_enable_network;
@@ -21,7 +25,6 @@ use std::ffi::CStr;
2125
use std::ffi::CString;
2226
use std::mem::MaybeUninit;
2327
use std::path::Path;
24-
use std::str;
2528
use thiserror::Error;
2629

2730
pub trait CoordinateType: Float + Copy + PartialOrd + Debug {}
@@ -96,6 +99,16 @@ pub enum ProjError {
9699
DownloadError(String, String, u8),
97100
}
98101

102+
#[derive(Error, Debug)]
103+
pub enum ProjCreateError {
104+
#[error("A nul byte was found in the PROJ string definition or CRS argument: {0}")]
105+
ArgumentNulError(ffi::NulError),
106+
#[error("The underlying PROJ call failed: {0}")]
107+
ProjError(String),
108+
#[error("A UTF8 error occurred when constructing a PROJ error message")]
109+
ProjErrorMessageUtf8Error(std::str::Utf8Error),
110+
}
111+
99112
/// The bounding box of an area of use
100113
///
101114
/// In the case of an area of use crossing the antimeridian (longitude +/- 180 degrees),
@@ -124,14 +137,14 @@ impl Area {
124137
}
125138

126139
/// Easily get a String from the external library
127-
pub(crate) unsafe fn _string(raw_ptr: *const c_char) -> Result<String, ProjError> {
140+
pub(crate) unsafe fn _string(raw_ptr: *const c_char) -> Result<String, str::Utf8Error> {
128141
assert!(!raw_ptr.is_null());
129142
let c_str = CStr::from_ptr(raw_ptr);
130143
Ok(str::from_utf8(c_str.to_bytes())?.to_string())
131144
}
132145

133146
/// Look up an error message using the error code
134-
fn error_message(code: c_int) -> Result<String, ProjError> {
147+
fn error_message(code: c_int) -> Result<String, str::Utf8Error> {
135148
unsafe {
136149
let rv = proj_errno_string(code);
137150
_string(rv)
@@ -149,13 +162,17 @@ fn area_set_bbox(parea: *mut proj_sys::PJ_AREA, new_area: Option<Area>) {
149162
}
150163

151164
/// called by Proj::new and ProjBuilder::transform_new_crs
152-
fn transform_string(ctx: *mut PJ_CONTEXT, definition: &str) -> Option<Proj> {
153-
let c_definition = CString::new(definition).ok()?;
165+
fn transform_string(ctx: *mut PJ_CONTEXT, definition: &str) -> Result<Proj, ProjCreateError> {
166+
let c_definition =
167+
CString::new(definition).map_err(|e| ProjCreateError::ArgumentNulError(e))?;
154168
let new_c_proj = unsafe { proj_create(ctx, c_definition.as_ptr()) };
155169
if new_c_proj.is_null() {
156-
None
170+
let error_code = unsafe { proj_context_errno(ctx) };
171+
let message =
172+
error_message(error_code).map_err(|e| ProjCreateError::ProjErrorMessageUtf8Error(e))?;
173+
Err(ProjCreateError::ProjError(message))
157174
} else {
158-
Some(Proj {
175+
Ok(Proj {
159176
c_proj: new_c_proj,
160177
ctx,
161178
area: None,
@@ -164,15 +181,23 @@ fn transform_string(ctx: *mut PJ_CONTEXT, definition: &str) -> Option<Proj> {
164181
}
165182

166183
/// Called by new_known_crs and proj_known_crs
167-
fn transform_epsg(ctx: *mut PJ_CONTEXT, from: &str, to: &str, area: Option<Area>) -> Option<Proj> {
168-
let from_c = CString::new(from).ok()?;
169-
let to_c = CString::new(to).ok()?;
184+
fn transform_epsg(
185+
ctx: *mut PJ_CONTEXT,
186+
from: &str,
187+
to: &str,
188+
area: Option<Area>,
189+
) -> Result<Proj, ProjCreateError> {
190+
let from_c = CString::new(from).map_err(|e| ProjCreateError::ArgumentNulError(e))?;
191+
let to_c = CString::new(to).map_err(|e| ProjCreateError::ArgumentNulError(e))?;
170192
let proj_area = unsafe { proj_area_create() };
171193
area_set_bbox(proj_area, area);
172194
let new_c_proj =
173195
unsafe { proj_create_crs_to_crs(ctx, from_c.as_ptr(), to_c.as_ptr(), proj_area) };
174196
if new_c_proj.is_null() {
175-
None
197+
let error_code = unsafe { proj_context_errno(ctx) };
198+
let message =
199+
error_message(error_code).map_err(|e| ProjCreateError::ProjErrorMessageUtf8Error(e))?;
200+
Err(ProjCreateError::ProjError(message))
176201
} else {
177202
// Normalise input and output order to Lon, Lat / Easting Northing by inserting
178203
// An axis swap operation if necessary
@@ -182,7 +207,7 @@ fn transform_epsg(ctx: *mut PJ_CONTEXT, from: &str, to: &str, area: Option<Area>
182207
proj_destroy(new_c_proj);
183208
normalised
184209
};
185-
Some(Proj {
210+
Ok(Proj {
186211
c_proj: normalised,
187212
ctx,
188213
area: Some(proj_area),
@@ -226,7 +251,7 @@ pub trait Info {
226251
/// # Safety
227252
/// This method contains unsafe code.
228253
fn get_url_endpoint(&self) -> Result<String, ProjError> {
229-
unsafe { _string(proj_context_get_url_endpoint(self.ctx())) }
254+
Ok(unsafe { _string(proj_context_get_url_endpoint(self.ctx()))? })
230255
}
231256
}
232257

@@ -365,7 +390,7 @@ impl ProjBuilder {
365390
///
366391
/// # Safety
367392
/// This method contains unsafe code.
368-
pub fn proj(mut self, definition: &str) -> Option<Proj> {
393+
pub fn proj(mut self, definition: &str) -> Result<Proj, ProjCreateError> {
369394
let ctx = unsafe { std::mem::replace(&mut self.ctx, proj_context_create()) };
370395
transform_string(ctx, definition)
371396
}
@@ -408,7 +433,12 @@ impl ProjBuilder {
408433
///
409434
/// # Safety
410435
/// This method contains unsafe code.
411-
pub fn proj_known_crs(mut self, from: &str, to: &str, area: Option<Area>) -> Option<Proj> {
436+
pub fn proj_known_crs(
437+
mut self,
438+
from: &str,
439+
to: &str,
440+
area: Option<Area>,
441+
) -> Result<Proj, ProjCreateError> {
412442
let ctx = unsafe { std::mem::replace(&mut self.ctx, proj_context_create()) };
413443
transform_epsg(ctx, from, to, area)
414444
}
@@ -443,7 +473,7 @@ impl Proj {
443473
// is signalled by the choice of enum used as input to the PJ_COORD union
444474
// PJ_LP signals projection of geodetic coordinates, with output being PJ_XY
445475
// and vice versa, or using PJ_XY for conversion operations
446-
pub fn new(definition: &str) -> Option<Proj> {
476+
pub fn new(definition: &str) -> Result<Proj, ProjCreateError> {
447477
let ctx = unsafe { proj_context_create() };
448478
transform_string(ctx, definition)
449479
}
@@ -486,7 +516,11 @@ impl Proj {
486516
///
487517
/// # Safety
488518
/// This method contains unsafe code.
489-
pub fn new_known_crs(from: &str, to: &str, area: Option<Area>) -> Option<Proj> {
519+
pub fn new_known_crs(
520+
from: &str,
521+
to: &str,
522+
area: Option<Area>,
523+
) -> Result<Proj, ProjCreateError> {
490524
let ctx = unsafe { proj_context_create() };
491525
transform_epsg(ctx, from, to, area)
492526
}
@@ -1039,6 +1073,33 @@ mod test {
10391073
assert_relative_eq!(t.x(), 1450880.2910605022);
10401074
assert_relative_eq!(t.y(), 1141263.0111604782);
10411075
}
1076+
1077+
#[test]
1078+
fn test_from_crs_nul_error() {
1079+
match Proj::new_known_crs("\0", "EPSG:4326", None) {
1080+
Err(ProjCreateError::ArgumentNulError(_)) => (),
1081+
_ => unreachable!(),
1082+
}
1083+
1084+
match Proj::new_known_crs("EPSG:4326", "\0", None) {
1085+
Err(ProjCreateError::ArgumentNulError(_)) => (),
1086+
_ => unreachable!(),
1087+
}
1088+
}
1089+
1090+
#[test]
1091+
fn test_from_crs_error() {
1092+
match Proj::new_known_crs("EPSG:4326", "🦀", None) {
1093+
Err(ProjCreateError::ProjError(..)) => (),
1094+
_ => unreachable!(),
1095+
}
1096+
1097+
match Proj::new_known_crs("🦀", "EPSG:4326", None) {
1098+
Err(ProjCreateError::ProjError(..)) => (),
1099+
_ => unreachable!(),
1100+
}
1101+
}
1102+
10421103
#[test]
10431104
// Carry out a projection from geodetic coordinates
10441105
fn test_projection() {
@@ -1107,11 +1168,25 @@ mod test {
11071168
assert_relative_eq!(t.x(), 1450880.2910605022);
11081169
assert_relative_eq!(t.y(), 1141263.0111604782);
11091170
}
1171+
11101172
#[test]
11111173
// Test that instantiation fails wth bad proj string input
11121174
fn test_init_error() {
1113-
assert!(Proj::new("🦀").is_none());
1175+
match Proj::new("🦀") {
1176+
Err(ProjCreateError::ProjError(_)) => (),
1177+
_ => unreachable!(),
1178+
}
11141179
}
1180+
1181+
#[test]
1182+
// Test that instantiation fails wth bad proj string input
1183+
fn test_init_error_nul() {
1184+
match Proj::new("\0") {
1185+
Err(ProjCreateError::ArgumentNulError(_)) => (),
1186+
_ => unreachable!(),
1187+
}
1188+
}
1189+
11151190
#[test]
11161191
fn test_conversion_error() {
11171192
// because step 1 isn't an inverse conversion, it's expecting lon lat input

0 commit comments

Comments
 (0)