@@ -3,14 +3,18 @@ use libc::{c_char, c_double};
33use num_traits:: Float ;
44use 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" ) ]
1620use proj_sys:: proj_context_set_enable_network;
@@ -21,7 +25,6 @@ use std::ffi::CStr;
2125use std:: ffi:: CString ;
2226use std:: mem:: MaybeUninit ;
2327use std:: path:: Path ;
24- use std:: str;
2528use thiserror:: Error ;
2629
2730pub 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