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 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}