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