prust/
lib.rs

1pub mod prt {
2    use std::{collections, fmt, path};
3    use std::ffi;
4    use std::fmt::{Display, Formatter};
5    use std::ptr;
6    use derive_builder::Builder;
7
8    #[derive(Debug)]
9    pub struct PrtError {
10        pub message: String,
11        pub status: Option<Status>,
12    }
13
14    pub struct PrtContext {
15        handle: *const prt_ffi::Object,
16    }
17
18    unsafe impl Sync for PrtContext {} // handle is thread-safe
19
20    impl Drop for PrtContext {
21        fn drop(&mut self) {
22            unsafe {
23                prt_ffi::Object::destroy(&*self.handle);
24            }
25        }
26    }
27
28    impl Display for PrtContext {
29        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30            write!(f, "PrtContext native handle at {:p}", self.handle)
31        }
32    }
33
34    pub fn init(_extra_plugin_paths: Option<Vec<path::PathBuf>>,
35                initial_minimal_log_level: Option<LogLevel>) -> Result<Box<PrtContext>, PrtError>
36    {
37        // we include the built-in extension path by default
38        let cesdk_lib_path = crate::helpers::get_cesdk_path().join("lib");
39        if !cesdk_lib_path.exists() {
40            return Err(PrtError {
41                message: format!("Error while loading built-in extensions: {}", get_status_description(Status::STATUS_FILE_NOT_FOUND)),
42                status: Some(Status::STATUS_FILE_NOT_FOUND),
43            });
44        }
45        let cesdk_lib_dir_wchar_vec = crate::helpers::from_string_to_wchar_vec(cesdk_lib_path.to_str().unwrap());
46
47        // append additional extension dirs
48        // TODO: handle extra_plugin_paths
49
50        let plugins_dirs: [*const libc::wchar_t; 1] = [cesdk_lib_dir_wchar_vec.as_ptr()];
51        let log_level = initial_minimal_log_level.or(Some(LogLevel::LOG_WARNING));
52        unsafe {
53            let mut status = Status::STATUS_UNSPECIFIED_ERROR;
54            let prt_handle = prt_ffi::ffi_init(plugins_dirs.as_ptr(),
55                                               plugins_dirs.len(),
56                                               log_level.unwrap(),
57                                               ptr::addr_of_mut!(status));
58            return if (prt_handle != ptr::null()) && (status == Status::STATUS_OK) {
59                Ok(Box::new(PrtContext { handle: prt_handle }))
60            } else {
61                // TODO: add details and prt::Status handling
62                Err(PrtError {
63                    message: get_status_description(Status::STATUS_UNSPECIFIED_ERROR),
64                    status: Some(Status::STATUS_UNSPECIFIED_ERROR),
65                })
66            };
67        }
68    }
69
70    #[derive(PartialEq)]
71    #[derive(Debug)]
72    pub enum PrimitiveType {
73        Undefined(),
74        String(String),
75        Float(f64),
76        Bool(bool),
77        Int(i32),
78        StringArray(Vec<String>),
79        FloatArray(Vec<f64>),
80        BoolArray(Vec<bool>),
81        IntArray(Vec<i32>),
82    }
83
84    pub type EncoderOptions = collections::HashMap<String, PrimitiveType>;
85
86    #[derive(Clone, Debug)]
87    pub enum KeyOrUri {
88        Undefined,
89        Key(String),
90        Uri(String), // TODO: use a type which supports nested Uris
91    }
92
93    impl Default for KeyOrUri {
94        fn default() -> Self { KeyOrUri::Undefined }
95    }
96
97    impl KeyOrUri {
98        fn ffi_to_cstring(&self) -> ffi::CString {
99            match self {
100                KeyOrUri::Undefined => panic!("Undefined ResolveMap key or Uri!"),
101                KeyOrUri::Key(k) => ffi::CString::new(k.as_str()).unwrap(),
102                KeyOrUri::Uri(u) => ffi::CString::new(u.as_str()).unwrap(),
103            }
104        }
105    }
106
107    impl fmt::Display for KeyOrUri {
108        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109            match self {
110                KeyOrUri::Undefined => write!(f, "Undefined ResolveMap key or Uri!"),
111                KeyOrUri::Key(k) => write!(f, "{}", k),
112                KeyOrUri::Uri(u) => write!(f, "{}", u),
113            }
114        }
115    }
116
117    #[derive(Default, Builder, Debug)]
118    pub struct InitialShape {
119        vertex_coords: Vec<f64>,
120        indices: Vec<u32>,
121        face_counts: Vec<u32>,
122
123        rule_file: KeyOrUri,
124        start_rule: String,
125        random_seed: i32,
126        name: String,
127    }
128
129    pub trait Callbacks {}
130
131    #[derive(Default)]
132    pub struct FileCallbacks {}
133
134    impl Callbacks for FileCallbacks {}
135
136    pub fn generate<C>(initial_shapes: &Vec<Box<InitialShape>>,
137                       encoders: &Vec<String>,
138                       encoder_options: &Vec<EncoderOptions>,
139                       callbacks: &mut Box<C>) -> Status // todo: consistent error handling
140        where C: Callbacks
141    {
142        if encoders.len() != encoder_options.len() {
143            return Status::STATUS_ARGUMENTS_MISMATCH;
144        }
145
146        // wrap the initial shapes into an adaptor to have a mutable place
147        // where we can hold any owners of C pointers
148        let mut initial_shape_adaptors: Vec<prt_ffi::InitialShapeAdaptor> = initial_shapes.iter()
149            .map(|x| prt_ffi::InitialShapeAdaptor::adapt(x))
150            .collect();
151
152        let initial_shape_wrappers: Vec<prt_ffi::InitialShapeWrapper> = initial_shape_adaptors.iter_mut()
153            .map(|x: &mut prt_ffi::InitialShapeAdaptor| x.get_ffi_wrapper())
154            .collect();
155
156        let initial_shape_wrapper_ptr_vec: Vec<*const prt_ffi::InitialShapeWrapper> = initial_shape_wrappers.iter()
157            .map(|x| &*x as *const prt_ffi::InitialShapeWrapper)
158            .collect();
159
160        let occlusion_handles: *const u64 = ptr::null();
161
162        // TODO: probably better to stay UTF-8 on the rust side and convert in the native wrapper
163        let encoders_wchar_vec: Vec<Vec<libc::wchar_t>> = encoders.iter()
164            .map(|x| crate::helpers::from_string_to_wchar_vec(x.as_str()))
165            .collect();
166        let encoders_ptr_vec: Vec<*const libc::wchar_t> = encoders_wchar_vec.iter().map(|x| x.as_ptr()).collect();
167
168        let encoder_options_ptr_vec: *const prt_ffi::AttributeMap = ptr::null();
169
170        let callbacks_context: *mut C = callbacks.as_mut();
171        let callbacks_binding: Box<prt_ffi::AbstractCallbacksBinding<C>>
172            = Box::new(prt_ffi::AbstractCallbacksBinding { context: callbacks_context });
173        let callbacks_binding_ptr
174            = Box::into_raw(callbacks_binding) as *mut ffi::c_void;
175
176        let cache: *mut prt_ffi::Cache = ptr::null_mut();
177        let occl_set: *const prt_ffi::OcclusionSet = ptr::null();
178        let generate_options: *const prt_ffi::AttributeMap = ptr::null();
179
180        unsafe {
181            let status = prt_ffi::ffi_generate(initial_shape_wrapper_ptr_vec.as_ptr(),
182                                               initial_shape_wrapper_ptr_vec.len(),
183                                               occlusion_handles,
184                                               encoders_ptr_vec.as_ptr(), encoders_ptr_vec.len(),
185                                               encoder_options_ptr_vec,
186                                               callbacks_binding_ptr,
187                                               cache,
188                                               occl_set,
189                                               generate_options);
190
191            return status;
192        }
193    }
194
195    #[allow(non_camel_case_types)]
196    #[allow(dead_code)]
197    #[repr(C)]
198    pub enum LogLevel {
199        LOG_TRACE = 0,
200        LOG_DEBUG = 1,
201        LOG_INFO = 2,
202        LOG_WARNING = 3,
203        LOG_ERROR = 4,
204        LOG_FATAL = 5,
205        LOG_NO = 1000,
206    }
207
208    pub trait LogHandler {
209        fn handle_log_event(&mut self, msg: &str);
210    }
211
212    #[derive(Default)]
213    pub struct DefaultLogHandler {}
214
215    impl LogHandler for DefaultLogHandler {
216        fn handle_log_event(&mut self, msg: &str) {
217            println!("{}", msg);
218        }
219    }
220
221    pub fn add_log_handler<T>(log_handler: &mut Box<T>) where T: LogHandler {
222        unsafe extern "C" fn handle_log_event<T>(context: *mut T, cmsg: *const ffi::c_char)
223            where T: LogHandler
224        {
225            let handler_ref: &mut T = &mut *context;
226
227            let msg = ffi::CStr::from_ptr(cmsg).to_str().unwrap();
228            handler_ref.handle_log_event(msg);
229        }
230
231        let context: *mut T = log_handler.as_mut();
232        let binding: Box<prt_ffi::AbstractLogHandlerBinding<T>> = Box::new(prt_ffi::AbstractLogHandlerBinding {
233            handle_log_event,
234            context,
235        });
236
237        let binding_ptr: *mut ffi::c_void = Box::into_raw(binding) as *mut ffi::c_void;
238        unsafe {
239            prt_ffi::ffi_add_log_handler(binding_ptr);
240        }
241    }
242
243    pub fn remove_log_handler<T>(log_handler: &mut Box<T>) where T: LogHandler {
244        unsafe extern "C" fn handle_log_event<T>(_context: *mut T, _cmsg: *const ffi::c_char)
245            where T: LogHandler
246        {}
247
248        let context = log_handler.as_mut() as *mut T;
249        let binding: Box<prt_ffi::AbstractLogHandlerBinding<T>> = Box::new(prt_ffi::AbstractLogHandlerBinding {
250            handle_log_event,
251            context,
252        });
253
254        let binding_ptr: *mut ffi::c_void = Box::into_raw(binding) as *mut ffi::c_void;
255        unsafe {
256            prt_ffi::ffi_remove_log_handler(binding_ptr);
257        }
258    }
259
260    pub fn log(msg: &str, level: LogLevel) {
261        let cs_vec = crate::helpers::from_string_to_wchar_vec(msg);
262        unsafe {
263            prt_ffi::prt_log(cs_vec.as_ptr(), level);
264        }
265    }
266
267    #[allow(non_camel_case_types)]
268    #[allow(dead_code)]
269    #[derive(PartialEq)]
270    #[derive(Debug)]
271    #[repr(C)]
272    pub enum Status {
273        STATUS_OK,
274        STATUS_UNSPECIFIED_ERROR,
275        STATUS_OUT_OF_MEM,
276        STATUS_NO_LICENSE,
277
278        STATUS_NOT_ALL_IS_GENERATED,
279        STATUS_INCOMPATIBLE_IS,
280
281        STATUS_FILE_NOT_FOUND,
282        STATUS_FILE_ALREADY_EXISTS,
283        STATUS_COULD_NOT_OPEN_FILE,
284        STATUS_COULD_NOT_CLOSE_FILE,
285        STATUS_FILE_WRITE_FAILED,
286        STATUS_FILE_READ_FAILED,
287        STATUS_FILE_SEEK_FAILED,
288        STATUS_FILE_TELL_FAILED,
289        STATUS_NO_SEEK,
290        STATUS_EMPTY_FILE,
291        STATUS_INVALID_URI,
292
293        STATUS_STREAM_ADAPTOR_NOT_FOUND,
294        STATUS_RESOLVEMAP_PROVIDER_NOT_FOUND,
295        STATUS_DECODER_NOT_FOUND,
296        STATUS_ENCODER_NOT_FOUND,
297        STATUS_UNABLE_TO_RESOLVE,
298        STATUS_CHECK_ERROR_PARAM,
299        STATUS_KEY_NOT_FOUND,
300        STATUS_KEY_ALREADY_TAKEN,
301        STATUS_KEY_NOT_SUPPORTED,
302        STATUS_STRING_TRUNCATED,
303        STATUS_ILLEGAL_CALLBACK_OBJECT,
304        STATUS_ILLEGAL_LOG_HANDLER,
305        STATUS_ILLEGAL_LOG_LEVEL,
306        STATUS_ILLEGAL_VALUE,
307        STATUS_NO_RULEFILE,
308        STATUS_NO_INITIAL_SHAPE,
309        STATUS_CGB_ERROR,
310        STATUS_NOT_INITIALIZED,
311        STATUS_ALREADY_INITIALIZED,
312        STATUS_INCONSISTENT_TEXTURE_PARAMS,
313        STATUS_CANCELED,
314        STATUS_UNKNOWN_ATTRIBUTE,
315        STATUS_UNKNOWN_RULE,
316        STATUS_ARGUMENTS_MISMATCH,
317        STATUS_BUFFER_TO_SMALL,
318        STATUS_UNKNOWN_FORMAT,
319        STATUS_ENCODE_FAILED,
320        STATUS_ATTRIBUTES_ALREADY_SET,
321        STATUS_ATTRIBUTES_NOT_SET,
322        STATUS_GEOMETRY_ALREADY_SET,
323        STATUS_GEOMETRY_NOT_SET,
324        STATUS_ILLEGAL_GEOMETRY,
325        STATUS_NO_GEOMETRY,
326    }
327
328    pub fn get_status_description(status: Status) -> String {
329        unsafe {
330            let status_description_cchar_ptr = prt_ffi::ffi_get_status_description(status);
331            let status_description_cstr = ffi::CStr::from_ptr(status_description_cchar_ptr);
332            let status_description = status_description_cstr.to_str().unwrap_or_default();
333            return String::from(status_description);
334        }
335    }
336
337    pub struct PrtVersion {
338        pub version_major: i32,
339        pub version_minor: i32,
340        pub version_build: i32,
341        pub version_string: String,
342
343        pub name: String,
344        pub full_name: String,
345
346        pub build_config: String,
347        pub build_os: String,
348        pub build_arch: String,
349        pub build_tc: String,
350        pub build_date: String,
351
352        pub cga_version_major: i32,
353        pub cga_version_minor: i32,
354        pub cga_version_string: String,
355
356        pub cgac_version_major: i32,
357        pub cgac_version_minor: i32,
358        pub cgac_version_string: String,
359    }
360
361    pub fn get_version() -> Result<PrtVersion, PrtError> {
362        unsafe {
363            let version_ptr = prt_ffi::ffi_get_version();
364            if version_ptr == ptr::null() {
365                return Err(PrtError {
366                    message: "Could not get PRT version info".to_string(),
367                    status: None,
368                });
369            }
370            let version_ref = &*version_ptr;
371            let ver = PrtVersion {
372                version_major: version_ref.version_major,
373                version_minor: version_ref.version_minor,
374                version_build: version_ref.version_build,
375                version_string: crate::helpers::from_char_ptr_to_string(version_ref.version),
376                name: crate::helpers::from_char_ptr_to_string(version_ref.name),
377                full_name: crate::helpers::from_char_ptr_to_string(version_ref.full_name),
378                build_config: crate::helpers::from_char_ptr_to_string(version_ref.build_config),
379                build_os: crate::helpers::from_char_ptr_to_string(version_ref.build_os),
380                build_arch: crate::helpers::from_char_ptr_to_string(version_ref.build_arch),
381                build_tc: crate::helpers::from_char_ptr_to_string(version_ref.build_tc),
382                build_date: crate::helpers::from_char_ptr_to_string(version_ref.build_date),
383                cga_version_major: version_ref.cga_version_major,
384                cga_version_minor: version_ref.cga_version_minor,
385                cga_version_string: crate::helpers::from_char_ptr_to_string(version_ref.cga_version),
386                cgac_version_major: version_ref.cgac_version_major,
387                cgac_version_minor: version_ref.cgac_version_minor,
388                cgac_version_string: crate::helpers::from_char_ptr_to_string(version_ref.cgac_version),
389            };
390            Ok(ver)
391        }
392    }
393
394    mod prt_ffi {
395        use std::ffi;
396        use std::ptr::null;
397
398        #[repr(C)]
399        pub(crate) struct Object {
400            dummy: i8, // to avoid the "unsafe FFI object" warning
401        }
402
403        impl Object {
404            pub(crate) fn destroy(&self) {}
405        }
406
407        extern "C" {
408            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
409            #[link_name = "\u{1}_ZN3prt4initEPKPKwmNS_8LogLevelEPNS_6StatusE"]
410            pub(crate) fn ffi_init(prt_plugins: *const *const libc::wchar_t,
411                                   prt_plugins_count: libc::size_t,
412                                   log_level: crate::prt::LogLevel,
413                                   status: *mut crate::prt::Status) -> *const Object;
414        }
415
416        #[repr(C)]
417        pub(crate) struct AttributeMap {
418            dummy: i32,
419        }
420
421        #[repr(C)]
422        struct ResolveMap {
423            dummy: i32,
424        }
425
426        #[repr(C)]
427        pub(crate) struct InitialShapeWrapper {
428            // see cpp/bindings.h
429            vertex_coords: *const f64,
430            vertex_coords_count: libc::size_t,
431            indices: *const u32,
432            indices_count: libc::size_t,
433            face_counts: *const u32,
434            face_counts_count: libc::size_t,
435
436            rule_file: *const ffi::c_char,
437            start_rule: *const ffi::c_char,
438            random_seed: i32,
439            name: *const ffi::c_char,
440            attributes: *const AttributeMap,
441            resolve_map: *const ResolveMap,
442        }
443
444        pub(crate) struct InitialShapeAdaptor<'a> {
445            initial_shape: &'a Box<crate::prt::InitialShape>,
446
447            ffi_rule_file_owner: ffi::CString,
448            ffi_start_rule_owner: ffi::CString,
449            ffi_name_owner: ffi::CString,
450        }
451
452        impl InitialShapeAdaptor<'_> {
453            pub(crate) fn adapt(initial_shape: &Box<crate::prt::InitialShape>) -> InitialShapeAdaptor {
454                InitialShapeAdaptor {
455                    initial_shape,
456                    ffi_rule_file_owner: initial_shape.rule_file.ffi_to_cstring(),
457                    ffi_start_rule_owner: ffi::CString::new(initial_shape.start_rule.as_bytes()).unwrap(),
458                    ffi_name_owner: ffi::CString::new(initial_shape.name.as_bytes()).unwrap(),
459                }
460            }
461
462            pub(crate) fn get_ffi_wrapper(&self) -> InitialShapeWrapper {
463                return InitialShapeWrapper {
464                    vertex_coords: self.initial_shape.vertex_coords.as_ptr(),
465                    vertex_coords_count: self.initial_shape.vertex_coords.len(),
466                    indices: self.initial_shape.indices.as_ptr(),
467                    indices_count: self.initial_shape.indices.len(),
468                    face_counts: self.initial_shape.face_counts.as_ptr(),
469                    face_counts_count: self.initial_shape.face_counts.len(),
470                    rule_file: self.ffi_rule_file_owner.as_ptr(),
471                    start_rule: self.ffi_start_rule_owner.as_ptr(),
472                    random_seed: self.initial_shape.random_seed,
473                    name: self.ffi_name_owner.as_ptr(),
474                    attributes: null(), // TODO
475                    resolve_map: null(), // TODO
476                };
477            }
478        }
479
480        #[repr(C)]
481        pub(crate) struct Cache {
482            dummy: i32,
483        }
484
485        #[repr(C)]
486        pub(crate) struct OcclusionSet {
487            dummy: i32,
488        }
489
490        #[repr(C)]
491        pub(crate) struct AbstractCallbacksBinding<T> where T: crate::prt::Callbacks {
492            pub(crate) context: *mut T,
493        }
494
495        #[link(name = "bindings", kind = "static")]
496        extern "C" {
497            pub(crate) fn ffi_generate(initial_shapes: *const *const InitialShapeWrapper,
498                                       initial_shapes_count: libc::size_t,
499                                       occlusion_handles: *const u64, // see prt::OcclusionSet::Handle
500                                       encoders: *const *const libc::wchar_t,
501                                       encoders_count: libc::size_t,
502                                       encoder_options: *const AttributeMap,
503                                       callbacks: *mut ffi::c_void,
504                                       cache: *mut Cache,
505                                       occl_set: *const OcclusionSet,
506                                       generate_options: *const AttributeMap) -> crate::prt::Status;
507        }
508
509        #[repr(C)]
510        pub(crate) struct AbstractLogHandlerBinding<T> where T: crate::prt::LogHandler {
511            pub(crate) handle_log_event: unsafe extern fn(*mut T, msg: *const ffi::c_char),
512            pub(crate) context: *mut T,
513        }
514
515        #[link(name = "bindings", kind = "static")]
516        extern "C" {
517            pub(crate) fn ffi_add_log_handler(log_handler: *mut ffi::c_void);
518            pub(crate) fn ffi_remove_log_handler(log_handler: *mut ffi::c_void);
519        }
520
521        extern "C" {
522            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
523            #[link_name = "\u{1}_ZN3prt3logEPKwNS_8LogLevelE"]
524            pub(crate) fn prt_log(msg: *const libc::wchar_t, level: crate::prt::LogLevel);
525        }
526
527        #[repr(C)]
528        pub(crate) struct Version {
529            pub(crate) version_major: i32,
530            pub(crate) version_minor: i32,
531            pub(crate) version_build: i32,
532
533            pub(crate) name: *const ffi::c_char,
534            pub(crate) full_name: *const ffi::c_char,
535            pub(crate) version: *const ffi::c_char,
536            pub(crate) build_config: *const ffi::c_char,
537            pub(crate) build_os: *const ffi::c_char,
538            pub(crate) build_arch: *const ffi::c_char,
539            pub(crate) build_tc: *const ffi::c_char,
540            pub(crate) build_date: *const ffi::c_char,
541
542            pub(crate) name_w: *const libc::wchar_t,
543            full_name_w: *const libc::wchar_t,
544            version_w: *const libc::wchar_t,
545            build_config_w: *const libc::wchar_t,
546            build_os_w: *const libc::wchar_t,
547            build_arch_w: *const libc::wchar_t,
548            build_tc_w: *const libc::wchar_t,
549            pub(crate) build_date_w: *const libc::wchar_t,
550
551            pub(crate) cga_version_major: i32,
552            pub(crate) cga_version_minor: i32,
553            pub(crate) cga_version: *const ffi::c_char,
554            cga_version_w: *const i32,
555
556            pub(crate) cgac_version_major: i32,
557            pub(crate) cgac_version_minor: i32,
558            pub(crate) cgac_version: *const ffi::c_char,
559            cgac_version_w: *const i32,
560        }
561
562        extern "C" {
563            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
564            #[link_name = "\u{1}_ZN3prt10getVersionEv"]
565            pub(crate) fn ffi_get_version() -> *const Version;
566        }
567
568        extern "C" {
569            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
570            #[link_name = "\u{1}_ZN3prt20getStatusDescriptionENS_6StatusE"]
571            pub(crate) fn ffi_get_status_description(input: crate::prt::Status) -> *const ffi::c_char;
572        }
573    }
574
575    #[cfg(test)]
576    mod tests {
577        use super::*;
578
579        fn assert_cstring(prefix: &str, raw_val: *const ffi::c_char, expected_val: &str) {
580            let val = crate::helpers::from_char_ptr_to_string(raw_val);
581            assert_string(prefix, val, expected_val);
582        }
583
584        fn assert_wcstring(prefix: &str, raw_val: *const libc::wchar_t, expected_val: &str) {
585            let val = crate::helpers::from_wchar_ptr_to_string(raw_val);
586            assert_string(prefix, val, expected_val);
587        }
588
589        fn assert_string(prefix: &str, raw_val: String, expected_val: &str) {
590            assert_eq!(raw_val, expected_val, "{}: assertion error", prefix);
591        }
592
593        fn assert_int(prefix: &str, raw_val: i32, expected_val: i32) {
594            assert_eq!(raw_val, expected_val, "{}: assertion error", prefix);
595        }
596
597        #[test]
598        fn prt_get_version() {
599            unsafe {
600                let ver = &*prt_ffi::ffi_get_version();
601                assert_cstring("prt::Version::mName", ver.name, "ArcGIS Procedural Runtime");
602                assert_cstring("prt::Version::mVersion", ver.version, "2.7.8538");
603                assert_cstring("prt::Version::mBuildConfig", ver.build_config, "PRT_BC_REL");
604                assert_cstring("prt::Version::mBuildOS", ver.build_os, "linux");
605                assert_cstring("prt::Version::mBuildArch", ver.build_arch, "x86_64");
606                assert_cstring("prt::Version::mBuildTC", ver.build_tc, "PRT_TC_GCC93");
607                assert_cstring("prt::Version::mBuildDate", ver.build_date, "2022-10-04 15:48");
608
609                assert_wcstring("prt::Version::mwName", ver.name_w, "ArcGIS Procedural Runtime");
610                assert_wcstring("prt::Version::mwBuildDate", ver.build_date_w, "2022-10-04 15:48");
611
612                assert_int("prt::Version::mCGAVersionMajor", ver.cga_version_major, 2022);
613                assert_int("prt::Version::mCGAVersionMinor", ver.cga_version_minor, 1);
614
615                assert_int("prt::Version::mCGACVersionMajor", ver.cgac_version_major, 1);
616                assert_int("prt::Version::mCGACVersionMinor", ver.cgac_version_minor, 19);
617            }
618        }
619
620        #[test]
621        fn prt_get_status_description() {
622            unsafe {
623                let status_description_cchar_ptr = prt_ffi::ffi_get_status_description(Status::STATUS_OUT_OF_MEM);
624                let status_description = crate::helpers::from_char_ptr_to_string(status_description_cchar_ptr);
625                assert_eq!(status_description, "Out of memory.");
626            }
627        }
628
629        #[test]
630        fn create_attribute_map() {
631            let mut map = collections::HashMap::new();
632            map.insert("foo".to_string(), PrimitiveType::String("bar".to_string()));
633            assert_eq!(map.get("foo"), Some(&PrimitiveType::String("bar".to_string())));
634        }
635    }
636}
637
638mod helpers {
639    use std::{ffi, fs, path};
640
641    pub(crate) fn from_char_ptr_to_string(cchar_ptr: *const ffi::c_char) -> String {
642        let val_cstr = unsafe { ffi::CStr::from_ptr(cchar_ptr) };
643        return val_cstr.to_str().unwrap_or_default().to_string();
644    }
645
646    #[allow(dead_code)]
647    #[cfg(target_os = "linux")]
648    fn wchar_is_utf32() -> bool {
649        true
650    }
651
652    #[allow(dead_code)]
653    #[cfg(target_os = "windows")]
654    fn wchar_is_utf32() -> bool {
655        false
656    }
657
658    #[allow(dead_code)]
659    pub fn from_wchar_ptr_to_string(ptr: *const libc::wchar_t) -> String {
660        assert!(!ptr.is_null());
661        let ptr_len = unsafe { libc::wcslen(ptr) };
662        assert!(ptr_len > 0);
663        return if wchar_is_utf32() {
664            let widestring_result = unsafe { widestring::U32CString::from_ptr(ptr as *const u32, ptr_len) };
665            let cstring = widestring_result.expect("could not convert wchar_t array to UTF32 string");
666            cstring.to_string().expect("could not convert to Rust string")
667        } else {
668            let widestring_result = unsafe { widestring::U16CString::from_ptr(ptr as *const u16, ptr_len) };
669            let cstring = widestring_result.expect("could not convert wchar_t array to UTF16 string");
670            cstring.to_string().expect("could not convert to Rust string")
671        };
672    }
673
674    pub fn from_string_to_wchar_vec(msg: &str) -> Vec<libc::wchar_t> {
675        return if cfg!(linux) {
676            let wide_msg = widestring::U32CString::from_str(msg)
677                .expect("cannot convert to UTF-32/wchar_t");
678            wide_msg.into_vec_with_nul().iter().map(|&e| e as libc::wchar_t).collect()
679        } else {
680            let wide_msg = widestring::U16CString::from_str(msg)
681                .expect("cannot convert to UTF-16/wchar_t");
682            wide_msg.into_vec_with_nul().iter().map(|&e| e as libc::wchar_t).collect()
683        };
684    }
685
686    // TODO: deduplicate with get_dependencies_path in build.rs
687    pub fn get_cesdk_path() -> path::PathBuf {
688        let out_dir = env!("OUT_DIR");
689        let crate_root = path::PathBuf::from(out_dir);
690        let deps_path = crate_root.join("prust_custom_deps");
691        let cesdk_path_entry = fs::read_dir(deps_path)
692            .expect("cannot read deps dir")
693            .filter(|p| p.is_ok() && p.as_ref().unwrap().file_type().unwrap().is_dir())
694            .find(|p| p.as_ref().unwrap()
695                .path().file_name().unwrap()
696                .to_str().unwrap().starts_with("esri_ce_sdk-"));
697        return cesdk_path_entry.expect("could not find cesdk lib dir").unwrap().path();
698    }
699}