rasn_compiler/generator/rasn/
mod.rs

1use std::{
2    env,
3    error::Error,
4    io::{self, Write},
5    path::PathBuf,
6    process::{Command, Stdio},
7    str::FromStr,
8};
9
10use crate::{error::CompilerError, intermediate::*};
11use proc_macro2::TokenStream;
12use quote::{quote, ToTokens};
13
14#[cfg(target_family = "wasm")]
15use wasm_bindgen::prelude::*;
16
17use super::{
18    error::{GeneratorError, GeneratorErrorType},
19    Backend, GeneratedModule,
20};
21
22mod builder;
23mod template;
24mod utils;
25
26#[derive(Debug, Default)]
27/// A compiler backend that generates bindings to be used with
28/// the `rasn` framework for rust.
29pub struct Rasn {
30    config: Config,
31    tagging_environment: TaggingEnvironment,
32    extensibility_environment: ExtensibilityEnvironment,
33}
34
35#[cfg_attr(target_family = "wasm", wasm_bindgen(getter_with_clone))]
36#[derive(Debug)]
37/// A configuration for the [Rasn] backend
38pub struct Config {
39    /// ASN.1 Open Types are represented as the `rasn::types::Any` type,
40    /// which holds a binary `content`. If `opaque_open_types` is `false`,
41    /// the compiler will generate additional de-/encode methods for
42    /// all rust types that hold an open type.
43    /// For example, bindings for a `SEQUENCE` with a field of Open Type
44    /// value will include a method for explicitly decoding the Open Type field.
45    /// _Non-opaque open types are still experimental. If you have trouble_
46    /// _generating correct bindings, switch back to opaque open types._
47    pub opaque_open_types: bool,
48    /// The compiler will try to match module import dependencies of the ASN.1
49    /// module as close as possible, importing only those types from other modules
50    /// that are imported in the ASN.1 module. If the `default_wildcard_imports`
51    /// is set to `true` , the compiler will import the entire module using
52    /// the wildcard `*` for each module that the input ASN.1 module imports from.
53    pub default_wildcard_imports: bool,
54    /// To make working with the generated types a bit more ergonomic, the compiler
55    /// can generate `From` impls for the wrapper inner types in a `CHOICE`, as long
56    /// as the generated impls are not ambiguous.
57    /// This is disabled by default to generate less code, but can be enabled with
58    /// `generate_from_impls` set to `true`.
59    pub generate_from_impls: bool,
60    /// Stringified paths to items that will be imported into all generated modules with a
61    /// [use declaration](https://doc.rust-lang.org/reference/items/use-declarations.html).
62    /// For example `vec![String::from("my::module::*"), String::from("path::to::my::Struct")]`.
63    pub custom_imports: Vec<String>,
64    /// Annotations to be added to all generated rust types of the bindings. Each vector element
65    /// will generate a new line of annotations. Note that the compiler will automatically add all pound-derives
66    /// needed by `rasn` __except__ `Eq` and `Hash`, which are needed only when working with `SET`s.
67    ///
68    /// Default: `vec![String::from("#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)]")]`
69    pub type_annotations: Vec<String>,
70}
71
72#[cfg(target_family = "wasm")]
73#[wasm_bindgen]
74impl Config {
75    #[wasm_bindgen(constructor)]
76    pub fn new(
77        opaque_open_types: bool,
78        default_wildcard_imports: bool,
79        generate_from_impls: Option<bool>,
80        custom_imports: Option<Box<[String]>>,
81        type_annotations: Option<Box<[String]>>,
82    ) -> Self {
83        Self {
84            opaque_open_types,
85            default_wildcard_imports,
86            generate_from_impls: generate_from_impls.unwrap_or(false),
87            custom_imports: custom_imports.map_or(Vec::new(), |c| c.into_vec()),
88            type_annotations: type_annotations
89                .map_or(Config::default().type_annotations, |c| c.into_vec()),
90        }
91    }
92}
93
94impl Default for Config {
95    fn default() -> Self {
96        Self {
97            opaque_open_types: true,
98            default_wildcard_imports: false,
99            generate_from_impls: false,
100            custom_imports: Vec::default(),
101            type_annotations: vec![String::from(
102                "#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq, Hash)]",
103            )],
104        }
105    }
106}
107
108impl Backend for Rasn {
109    type Config = Config;
110
111    const FILE_EXTENSION: &'static str = ".rs";
112
113    fn new(
114        config: Self::Config,
115        tagging_environment: TaggingEnvironment,
116        extensibility_environment: ExtensibilityEnvironment,
117    ) -> Self {
118        Self {
119            config,
120            extensibility_environment,
121            tagging_environment,
122        }
123    }
124
125    fn from_config(config: Self::Config) -> Self {
126        Self {
127            config,
128            ..Default::default()
129        }
130    }
131
132    fn config(&self) -> &Self::Config {
133        &self.config
134    }
135
136    fn generate_module(
137        &mut self,
138        tlds: Vec<ToplevelDefinition>,
139    ) -> Result<GeneratedModule, GeneratorError> {
140        if let Some((module_ref, _)) = tlds.first().and_then(|tld| tld.get_index().cloned()) {
141            let module = module_ref.borrow();
142            self.tagging_environment = module.tagging_environment;
143            self.extensibility_environment = module.extensibility_environment;
144            let name = self.to_rust_snake_case(&module.name);
145            let custom_imports = self
146                .config
147                .custom_imports
148                .iter()
149                .map(|i| TokenStream::from_str(i.as_str()).map(|i| quote!(use #i;)))
150                .collect::<Result<Vec<_>, _>>()
151                .map_err(|e| GeneratorError {
152                    details: e.to_string(),
153                    ..Default::default()
154                })?;
155            let imports = module.imports.iter().map(|import| {
156                let module =
157                    self.to_rust_snake_case(&import.global_module_reference.module_reference);
158                let mut usages = Some(vec![]);
159                'imports: for usage in &import.types {
160                    if usage.contains("{}") || usage.chars().all(|c| c.is_uppercase() || c == '-') {
161                        usages = None;
162                        break 'imports;
163                    } else if usage.starts_with(|c: char| c.is_lowercase()) {
164                        if let Some(us) = usages.as_mut() {
165                            us.push(self.to_rust_const_case(usage).to_token_stream())
166                        }
167                    } else if usage.starts_with(|c: char| c.is_uppercase()) {
168                        if let Some(us) = usages.as_mut() {
169                            us.push(self.to_rust_title_case(usage).to_token_stream())
170                        }
171                    }
172                }
173                let used_imports = if self.config.default_wildcard_imports {
174                    vec![TokenStream::from_str("*").unwrap()]
175                } else {
176                    usages.unwrap_or(vec![TokenStream::from_str("*").unwrap()])
177                };
178                quote!(use super:: #module::{ #(#used_imports),* };)
179            });
180            let (pdus, warnings): (Vec<TokenStream>, Vec<CompilerError>) =
181                tlds.into_iter().fold((vec![], vec![]), |mut acc, tld| {
182                    match self.generate_tld(tld) {
183                        Ok(s) => {
184                            acc.0.push(s);
185                            acc
186                        }
187                        Err(e) => {
188                            acc.1.push(e.into());
189                            acc
190                        }
191                    }
192                });
193            Ok(GeneratedModule {
194                generated: Some(quote! {
195                #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, unused,
196                        clippy::too_many_arguments,)]
197                pub mod #name {
198                    extern crate alloc;
199
200                    use core::borrow::Borrow;
201                    use rasn::prelude::*;
202                    use lazy_static::lazy_static;
203                    #(#custom_imports)*
204                    #(#imports)*
205
206                    #(#pdus)*
207                }
208            }.to_string()), warnings})
209        } else {
210            Ok(GeneratedModule::empty())
211        }
212    }
213
214    fn format_bindings(bindings: &str) -> Result<String, CompilerError> {
215        Self::internal_fmt(bindings).map_err(|e| {
216            GeneratorError {
217                top_level_declaration: None,
218                details: e.to_string(),
219                kind: GeneratorErrorType::FormattingError,
220            }
221            .into()
222        })
223    }
224
225    fn generate(&self, tld: ToplevelDefinition) -> Result<String, GeneratorError> {
226        self.generate_tld(tld).map(|ts| ts.to_string())
227    }
228}
229
230impl Rasn {
231    fn get_rustfmt_path() -> Result<PathBuf, Box<dyn Error>> {
232        // Try ~/.cargo/bin/rustfmt style paths first
233        if let Ok(path) = env::var("CARGO_HOME").map(PathBuf::from).map(|mut path| {
234            path.push("bin/rustfmt");
235            path
236        }) {
237            if path.exists() {
238                return Ok(path);
239            }
240        }
241
242        // Alternatively, maybe rustfmt and cargo are in the same directory
243        if let Ok(path) = env::var("CARGO").map(PathBuf::from).map(|mut path| {
244            path.set_file_name("rustfmt");
245            path
246        }) {
247            if path.exists() {
248                return Ok(path);
249            }
250        }
251
252        Err("No rustfmt found.".into())
253    }
254
255    fn internal_fmt(bindings: &str) -> Result<String, Box<dyn Error>> {
256        let rustfmt = Self::get_rustfmt_path()?;
257        let mut cmd = Command::new(rustfmt);
258
259        cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
260
261        let mut child = cmd.spawn()?;
262        let mut child_stdin = child.stdin.take().unwrap();
263        let mut child_stdout = child.stdout.take().unwrap();
264
265        // Write to stdin in a new thread, so that we can read from stdout on this
266        // thread. This keeps the child from blocking on writing to its stdout which
267        // might block us from writing to its stdin.
268        let bindings = bindings.to_owned();
269        let stdin_handle = ::std::thread::spawn(move || {
270            let _ = child_stdin.write_all(bindings.as_bytes());
271            bindings
272        });
273
274        let mut output = vec![];
275        io::copy(&mut child_stdout, &mut output)?;
276
277        let status = child.wait()?;
278        let bindings = stdin_handle.join().expect(
279            "The thread writing to rustfmt's stdin doesn't do \
280             anything that could panic",
281        );
282
283        match String::from_utf8(output) {
284            Ok(bindings) => match status.code() {
285                Some(0) => Ok(bindings),
286                Some(2) => Err(Box::new(io::Error::new(
287                    io::ErrorKind::Other,
288                    "Rustfmt parsing errors.".to_string(),
289                ))),
290                Some(3) => Ok(bindings),
291                _ => Err(Box::new(io::Error::new(
292                    io::ErrorKind::Other,
293                    "Internal rustfmt error".to_string(),
294                ))),
295            },
296            _ => Ok(bindings),
297        }
298    }
299}