Skip to main content

eric_sdk/
eric.rs

1use crate::{
2    config::{CertificateConfig, PrintConfig},
3    error::EricError,
4    error_code::ErrorCode,
5    response::{EricResponse, ResponseBuffer},
6    utils::ToCString,
7    ProcessingFlag,
8};
9use anyhow::{anyhow, Context};
10use eric_bindings::{
11    EricBearbeiteVorgang, EricBeende, EricDekodiereDaten, EricEntladePlugins, EricHoleFehlerText,
12    EricInitialisiere,
13};
14use std::{
15    env::{self},
16    path::Path,
17    ptr,
18};
19
20/// A structure to manage the Eric instance from the shared C library.
21///
22/// Use [`Eric::new`] to initialize Eric. Closes Eric when dropped.
23pub struct Eric;
24
25impl Eric {
26    /// Initializes a single-threaded Eric instance.
27    ///
28    /// The `log_path` specifies the path to the `eric.log` file.
29    pub fn new(log_path: &Path) -> Result<Self, EricError> {
30        println!("Initializing eric");
31
32        let plugin_path = env::var("PLUGIN_PATH").ok();
33        let plugin_ptr = match plugin_path {
34            Some(plugin_path) => {
35                println!(
36                    "Setting plugin path '{}'",
37                    Path::new(&plugin_path).display()
38                );
39
40                plugin_path.try_to_cstring()?.as_ptr()
41            }
42            None => ptr::null(),
43        };
44
45        println!("Setting log path '{}'", log_path.display());
46        println!("Logging to '{}'", log_path.join("eric.log").display());
47
48        let log_path = log_path.try_to_cstring()?;
49
50        let error_code = unsafe { EricInitialisiere(plugin_ptr, log_path.as_ptr()) };
51
52        match error_code {
53            x if x == ErrorCode::ERIC_OK as i32 => Ok(Eric),
54            error_code => Err(EricError::Internal(anyhow!(
55                "Can't init eric: {}",
56                error_code
57            ))),
58        }
59    }
60
61    /// Validates an XML file for a specific taxonomy.
62    ///
63    /// Optionally, a confirmation is printed to `pdf_path`.
64    pub fn validate(
65        &self,
66        xml: String,
67        taxonomy_type: &str,
68        taxonomy_version: &str,
69        pdf_path: Option<&str>,
70    ) -> Result<EricResponse, EricError> {
71        let processing_flag: ProcessingFlag;
72        let type_version = format!("{}_{}", taxonomy_type, taxonomy_version);
73        let print_config = if let Some(pdf_path) = pdf_path {
74            processing_flag = ProcessingFlag::Print;
75            Some(PrintConfig::new(pdf_path, &processing_flag)?)
76        } else {
77            processing_flag = ProcessingFlag::Validate;
78            None
79        };
80        Self::process(xml, type_version, processing_flag, print_config, None, None)
81    }
82
83    /// Sends an XML file for a specific taxonomy to the tax authorities.
84    ///
85    /// The Elster certificate is provided via environment variables
86    /// `CERTIFICATE_PATH` and `CERTIFICATE_PASSWORD`.
87    ///
88    /// Optionally, a confirmation is printed to `pdf_path`.
89    pub fn send(
90        &self,
91        xml: String,
92        taxonomy_type: &str,
93        taxonomy_version: &str,
94        pdf_path: Option<&str>,
95    ) -> Result<EricResponse, EricError> {
96        let certificate_path = env::var("CERTIFICATE_PATH")
97            .context("Missing environment variable 'CERTIFICATE_PATH'")?;
98        let certificate_password = env::var("CERTIFICATE_PASSWORD")
99            .context("Missing environment variable 'CERTIFICATE_PASSWORD'")?;
100
101        let processing_flag: ProcessingFlag;
102        let type_version = format!("{}_{}", taxonomy_type, taxonomy_version);
103        let print_config = if let Some(pdf_path) = pdf_path {
104            processing_flag = ProcessingFlag::SendAndPrint;
105            Some(PrintConfig::new(pdf_path, &processing_flag)?)
106        } else {
107            processing_flag = ProcessingFlag::Send;
108            None
109        };
110        let certificate_config = CertificateConfig::new(&certificate_path, &certificate_password)?;
111        Self::process(
112            xml,
113            type_version,
114            processing_flag,
115            print_config,
116            Some(certificate_config),
117            None,
118        )
119    }
120
121    /// Returns the error text for a specific error code.
122    pub fn get_error_text(&self, error_code: i32) -> Result<String, EricError> {
123        let response_buffer = ResponseBuffer::new()?;
124
125        unsafe {
126            EricHoleFehlerText(error_code, response_buffer.as_ptr());
127        }
128
129        Ok(response_buffer.read()?.to_string())
130    }
131
132    #[allow(dead_code)]
133    fn decrypt(
134        &self,
135        encrypted_file: &str,
136        certificate_config: CertificateConfig,
137    ) -> Result<i32, EricError> {
138        let encrypted_data = encrypted_file.try_to_cstring()?;
139        let response_buffer = ResponseBuffer::new()?;
140
141        let error_code = unsafe {
142            EricDekodiereDaten(
143                certificate_config.certificate.handle,
144                certificate_config.password.as_ptr(),
145                encrypted_data.as_ptr(),
146                response_buffer.as_ptr(),
147            )
148        };
149
150        Ok(error_code)
151    }
152
153    fn process(
154        xml: String,
155        type_version: String,
156        processing_flag: ProcessingFlag,
157        print_config: Option<PrintConfig>,
158        certificate_config: Option<CertificateConfig>,
159        transfer_code: Option<u32>,
160    ) -> Result<EricResponse, EricError> {
161        println!("Processing xml file");
162
163        match processing_flag {
164            ProcessingFlag::Validate => println!("Validating xml file"),
165            ProcessingFlag::Print => println!("Validating xml file"),
166            ProcessingFlag::Send => println!("Sending xml file"),
167            ProcessingFlag::SendAndPrint => println!("Send and print"),
168            ProcessingFlag::CheckHints => println!("Check hints"),
169            ProcessingFlag::ValidateWithoutDate => println!("Validate without release date"),
170        }
171
172        let xml = xml.try_to_cstring()?;
173        let type_version = type_version.try_to_cstring()?;
174
175        // Transfer_code should be NULL except for data retrieval; if
176        // transfer_code is not NULL in the other cases, it will be ignored
177        let transfer_code = match transfer_code {
178            Some(mut code) => &mut code,
179            None => ptr::null::<u32>() as *mut u32,
180        };
181
182        match &print_config {
183            Some(print_config) => println!(
184                "Printing confirmation to file '{}'",
185                print_config
186                    .pdf_path
187                    .to_str()
188                    .context("failed to convert path to string")?
189            ),
190            None => (),
191        }
192
193        let validation_response_buffer = ResponseBuffer::new()?;
194        let server_response_buffer = ResponseBuffer::new()?;
195
196        let error_code = unsafe {
197            EricBearbeiteVorgang(
198                xml.as_ptr(),
199                type_version.as_ptr(),
200                processing_flag.into_u32(),
201                // SAFETY: match a reference of print_config; otherwise
202                // print_config is moved, and print_parameter.as_ptr() would be
203                // dangling
204                match &print_config {
205                    Some(el) => el.print_parameter.as_ptr(),
206                    None => ptr::null(),
207                },
208                // SAFETY: match a reference of certificate_config; otherwise
209                // certificate_config is moved, and
210                // certificate_parameter.as_ptr() would be dangling
211                match &certificate_config {
212                    Some(el) => el.certificate_parameter.as_ptr(),
213                    None => ptr::null(),
214                },
215                transfer_code,
216                validation_response_buffer.as_ptr(),
217                server_response_buffer.as_ptr(),
218            )
219        };
220
221        let transfer_code = unsafe { transfer_code.as_ref() };
222
223        if let Some(code) = transfer_code {
224            println!("Transfer code: {}", code)
225        }
226
227        let validation_response = validation_response_buffer.read()?;
228        // TODO: parse server response via EricGetErrormessagesFromXMLAnswer()
229        let server_response = server_response_buffer.read()?;
230
231        if error_code != ErrorCode::ERIC_OK as i32 {
232            let response_buffer = ResponseBuffer::new()?;
233            unsafe {
234                EricHoleFehlerText(error_code, response_buffer.as_ptr());
235            }
236            let error_text = response_buffer.read()?;
237            return Err(EricError::ApiError {
238                code: error_code,
239                message: error_text.to_string(),
240                validation_response: validation_response.to_string(),
241                server_response: server_response.to_string(),
242            });
243        }
244
245        let response = EricResponse::new(
246            error_code,
247            validation_response.to_string(),
248            server_response.to_string(),
249        );
250
251        Ok(response)
252    }
253}
254
255impl Drop for Eric {
256    fn drop(&mut self) {
257        println!("Closing eric");
258
259        let error_code = unsafe { EricEntladePlugins() };
260
261        if error_code != ErrorCode::ERIC_OK as i32 {
262            println!("Error while unloading plugins: {}", error_code);
263        }
264
265        let error_code = unsafe { EricBeende() };
266
267        if error_code != ErrorCode::ERIC_OK as i32 {
268            println!("Can't close eric: {}", error_code)
269        }
270    }
271}