eric_sdk/
eric.rs

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