asn1rs_model/gen/
protobuf.rs

1use crate::gen::Generator;
2use crate::model::rust::rust_module_name;
3use crate::model::Protobuf;
4use crate::model::ProtobufType;
5use crate::model::{Definition, ObjectIdentifierComponent};
6use crate::model::{Model, ObjectIdentifier};
7use std::fmt::Error as FmtError;
8use std::fmt::Write;
9
10#[derive(Debug)]
11pub enum Error {
12    Fmt(FmtError),
13}
14
15impl From<FmtError> for Error {
16    fn from(e: FmtError) -> Self {
17        Error::Fmt(e)
18    }
19}
20
21#[allow(clippy::module_name_repetitions)]
22#[derive(Debug, Default)]
23pub struct ProtobufDefGenerator {
24    models: Vec<Model<Protobuf>>,
25}
26
27impl Generator<Protobuf> for ProtobufDefGenerator {
28    type Error = Error;
29
30    fn add_model(&mut self, model: Model<Protobuf>) {
31        self.models.push(model);
32    }
33
34    fn models(&self) -> &[Model<Protobuf>] {
35        &self.models[..]
36    }
37
38    fn models_mut(&mut self) -> &mut [Model<Protobuf>] {
39        &mut self.models[..]
40    }
41
42    fn to_string(&self) -> Result<Vec<(String, String)>, <Self as Generator<Protobuf>>::Error> {
43        let mut files = Vec::new();
44        for model in &self.models {
45            files.push(Self::generate_file(model)?);
46        }
47        Ok(files)
48    }
49}
50
51impl ProtobufDefGenerator {
52    pub fn generate_file(model: &Model<Protobuf>) -> Result<(String, String), Error> {
53        let file_name = Self::model_file_name(&model.name);
54        let mut content = String::new();
55        Self::append_header(&mut content, model)?;
56        Self::append_imports(&mut content, model)?;
57        for definition in &model.definitions {
58            Self::append_definition(&mut content, model, definition)?;
59        }
60        Ok((file_name, content))
61    }
62
63    pub fn append_header(target: &mut dyn Write, model: &Model<Protobuf>) -> Result<(), Error> {
64        writeln!(target, "syntax = 'proto3';")?;
65        writeln!(
66            target,
67            "package {};",
68            Self::model_to_package(&model.name, model.oid.as_ref())
69        )?;
70        writeln!(target)?;
71        Ok(())
72    }
73
74    pub fn append_imports(target: &mut dyn Write, model: &Model<Protobuf>) -> Result<(), Error> {
75        for import in &model.imports {
76            writeln!(target, "import '{}';", Self::model_file_name(&import.from))?;
77        }
78        writeln!(target)?;
79        Ok(())
80    }
81
82    pub fn append_definition(
83        target: &mut dyn Write,
84        model: &Model<Protobuf>,
85        Definition(name, protobuf): &Definition<Protobuf>,
86    ) -> Result<(), Error> {
87        match protobuf {
88            Protobuf::Enum(variants) => {
89                writeln!(target, "enum {} {{", name)?;
90                for (tag, variant) in variants.iter().enumerate() {
91                    Self::append_variant(target, name, variant, tag)?;
92                }
93                writeln!(target, "}}")?;
94            }
95            Protobuf::Message(fields) => {
96                writeln!(target, "message {} {{", name)?;
97                for (prev_tag, (field_name, field_type)) in fields.iter().enumerate() {
98                    Self::append_field(target, model, field_name, field_type, prev_tag + 1)?;
99                }
100                writeln!(target, "}}")?;
101            }
102        }
103        Ok(())
104    }
105
106    pub fn append_field(
107        target: &mut dyn Write,
108        model: &Model<Protobuf>,
109        name: &str,
110        role: &ProtobufType,
111        tag: usize,
112    ) -> Result<(), Error> {
113        writeln!(
114            target,
115            "    {} {}{};",
116            Self::role_to_full_type(role, model),
117            Self::field_name(name),
118            if let ProtobufType::OneOf(variants) = role {
119                let mut inner = String::new();
120                writeln!(&mut inner, " {{")?;
121                for (index, (variant_name, variant_type)) in variants.iter().enumerate() {
122                    writeln!(
123                        &mut inner,
124                        "      {} {} = {};",
125                        Self::role_to_full_type(variant_type, model),
126                        variant_name,
127                        index + 1
128                    )?;
129                }
130                write!(&mut inner, "    }}")?;
131                inner
132            } else {
133                format!(" = {}", tag)
134            }
135        )?;
136        Ok(())
137    }
138
139    pub fn append_variant(
140        target: &mut dyn Write,
141        base: &str,
142        variant: &str,
143        tag: usize,
144    ) -> Result<(), Error> {
145        // "Prefer prefixing enum values": https://developers.google.com/protocol-buffers/docs/style#enums
146        writeln!(
147            target,
148            "    {}_{} = {};",
149            Self::variant_name(base),
150            Self::variant_name(variant),
151            tag
152        )?;
153        Ok(())
154    }
155
156    pub fn role_to_full_type(role: &ProtobufType, model: &Model<Protobuf>) -> String {
157        match role {
158            ProtobufType::Complex(name) => {
159                let mut prefixed = String::new();
160                'outer: for import in &model.imports {
161                    for what in &import.what {
162                        if what.eq(name) {
163                            prefixed.push_str(&Self::model_to_package(
164                                &import.from,
165                                import.from_oid.as_ref(),
166                            ));
167                            prefixed.push('.');
168                            break 'outer;
169                        }
170                    }
171                }
172                prefixed.push_str(name);
173                prefixed
174            }
175            ProtobufType::Repeated(inner) => {
176                format!("repeated {}", Self::role_to_full_type(inner, model))
177            }
178            r => r.to_string(),
179        }
180    }
181
182    pub fn variant_name(name: &str) -> String {
183        let mut string = String::new();
184        let mut prev_upper = true;
185        for c in name.chars() {
186            match c {
187                '-' => string.push('_'),
188                u => {
189                    if !prev_upper && u.is_uppercase() {
190                        string.push('_');
191                    }
192                    string.push(u);
193                    prev_upper = u.is_uppercase();
194                }
195            };
196        }
197        string.to_uppercase()
198    }
199
200    pub fn field_name(name: &str) -> String {
201        name.replace('-', "_")
202    }
203
204    pub fn model_file_name(model: &str) -> String {
205        let mut name = Self::model_name(model, '_');
206        name.push_str(".proto");
207        name
208    }
209    pub fn model_name(model: &str, separator: char) -> String {
210        let mut out = String::new();
211        let mut prev_lowered = false;
212        let mut chars = model.chars().peekable();
213        while let Some(c) = chars.next() {
214            let mut lowered = false;
215            if c.is_uppercase() {
216                if !out.is_empty() {
217                    if !prev_lowered {
218                        out.push(separator);
219                    } else if let Some(next) = chars.peek() {
220                        if next.is_lowercase() {
221                            out.push(separator);
222                        }
223                    }
224                }
225                lowered = true;
226                out.push_str(&c.to_lowercase().to_string());
227            } else if c == '-' {
228                out.push(separator);
229            } else {
230                out.push(c);
231            }
232            prev_lowered = lowered;
233        }
234        out
235    }
236
237    pub fn model_to_package(path: &str, oid: Option<&ObjectIdentifier>) -> String {
238        if let Some(oid) = oid {
239            oid.iter()
240                .map(|oid| match oid {
241                    ObjectIdentifierComponent::NameForm(name)
242                    | ObjectIdentifierComponent::NameAndNumberForm(name, _) => {
243                        if name.chars().next().map_or(false, |c| !c.is_alphabetic()) {
244                            format!("_{}", name.replace('-', "_"))
245                        } else {
246                            name.replace('-', "_")
247                        }
248                    }
249                    ObjectIdentifierComponent::NumberForm(number) => {
250                        format!("_{}", number)
251                    }
252                })
253                .map(|name| rust_module_name(&name, false))
254                .collect::<Vec<String>>()
255                .join(".")
256        } else {
257            Self::model_name(&path.replace('_', "."), '.')
258        }
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn test_protobuf_variant_name() {
268        assert_eq!("ABC_DEF", ProtobufDefGenerator::variant_name("abc-def"));
269        assert_eq!("ABC_DEF", ProtobufDefGenerator::variant_name("AbcDef"));
270        assert_eq!("ABC_DEF", ProtobufDefGenerator::variant_name("ABcDef"));
271    }
272}