• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

getdozer / dozer / 6299724219

25 Sep 2023 12:58PM UTC coverage: 77.81% (+0.5%) from 77.275%
6299724219

push

github

chubei
fix: Add `BINDGEN_EXTRA_CLANG_ARGS` to cross compile rocksdb

50223 of 64546 relevant lines covered (77.81%)

148909.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.76
/dozer-api/src/generator/protoc/generator/implementation.rs
1
use crate::errors::GenerationError;
2
use crate::errors::GenerationError::ServiceNotFound;
3
use crate::generator::protoc::generator::{
4
    CountMethodDesc, DecimalDesc, DurationDesc, EventDesc, OnEventMethodDesc, PointDesc,
5
    QueryMethodDesc, RecordWithIdDesc, TokenMethodDesc, TokenResponseDesc,
6
};
7
use dozer_cache::dozer_log::schemas::EndpointSchema;
8
use dozer_types::log::error;
9
use dozer_types::serde::{self, Deserialize, Serialize};
10
use dozer_types::types::{FieldType, Schema};
11
use handlebars::Handlebars;
12
use inflector::Inflector;
13
use prost_reflect::{DescriptorPool, FieldDescriptor, Kind, MessageDescriptor};
14
use std::path::Path;
15

16
use super::{CountResponseDesc, NamedProto, QueryResponseDesc, RecordDesc, ServiceDesc};
17

18
const POINT_TYPE_CLASS: &str = "dozer.types.PointType";
19
const DURATION_TYPE_CLASS: &str = "dozer.types.DurationType";
20
const DECIMAL_TYPE_CLASS: &str = "dozer.types.RustDecimal";
21
const TIMESTAMP_TYPE_CLASS: &str = "google.protobuf.Timestamp";
22
const JSON_TYPE_CLASS: &str = "google.protobuf.Value";
23

24
#[derive(Debug, Clone, Serialize, Deserialize)]
39✔
25
#[serde(crate = "self::serde")]
26
struct ProtoMetadata {
27
    import_libs: Vec<String>,
28
    package_name: String,
29
    lower_name: String,
30
    plural_pascal_name: String,
31
    pascal_name: String,
32
    props: Vec<String>,
33
    version_field_id: usize,
34
    enable_token: bool,
35
    enable_on_event: bool,
36
}
37

38
pub struct ProtoGeneratorImpl<'a> {
39
    handlebars: Handlebars<'a>,
40
    schema: &'a EndpointSchema,
41
    names: Names,
42
}
43

44
impl<'a> ProtoGeneratorImpl<'a> {
45
    pub fn new(schema_name: &str, schema: &'a EndpointSchema) -> Result<Self, GenerationError> {
39✔
46
        let names = Names::new(schema_name, &schema.schema);
39✔
47
        let mut generator = Self {
39✔
48
            handlebars: Handlebars::new(),
39✔
49
            schema,
39✔
50
            names,
39✔
51
        };
39✔
52
        generator.register_template()?;
39✔
53
        Ok(generator)
39✔
54
    }
39✔
55

56
    fn register_template(&mut self) -> Result<(), GenerationError> {
39✔
57
        let main_template = include_str!("template/proto.tmpl");
39✔
58
        self.handlebars
39✔
59
            .register_template_string("main", main_template)
39✔
60
            .map_err(|e| GenerationError::HandlebarsTemplate(Box::new(e)))?;
39✔
61
        Ok(())
39✔
62
    }
39✔
63

64
    fn props(&self) -> Vec<String> {
39✔
65
        self.schema
39✔
66
            .schema
39✔
67
            .fields
39✔
68
            .iter()
39✔
69
            .enumerate()
39✔
70
            .zip(&self.names.record_field_names)
39✔
71
            .map(|((idx, field), field_name)| -> String {
39✔
72
                let optional = if field.nullable { "optional " } else { "" };
93✔
73
                let proto_type = convert_dozer_type_to_proto_type(field.typ.to_owned()).unwrap();
93✔
74
                format!("{optional}{proto_type} {field_name} = {};", idx + 1)
93✔
75
            })
93✔
76
            .collect()
39✔
77
    }
39✔
78

79
    pub fn libs_by_type(&self) -> Result<Vec<String>, GenerationError> {
39✔
80
        let type_need_import_libs = [TIMESTAMP_TYPE_CLASS, JSON_TYPE_CLASS];
39✔
81
        let mut libs_import: Vec<String> = self
39✔
82
            .schema
39✔
83
            .schema
39✔
84
            .fields
39✔
85
            .iter()
39✔
86
            .map(|field| convert_dozer_type_to_proto_type(field.to_owned().typ).unwrap())
93✔
87
            .filter(|proto_type| -> bool {
93✔
88
                type_need_import_libs.contains(&proto_type.to_owned().as_str())
93✔
89
            })
93✔
90
            .map(|proto_type| match proto_type.as_str() {
39✔
91
                TIMESTAMP_TYPE_CLASS => "google/protobuf/timestamp.proto".to_owned(),
3✔
92
                JSON_TYPE_CLASS => "google/protobuf/struct.proto".to_owned(),
×
93
                _ => "".to_owned(),
×
94
            })
39✔
95
            .collect();
39✔
96
        libs_import.push("types.proto".to_owned());
39✔
97
        libs_import.sort();
39✔
98
        libs_import.dedup();
39✔
99
        Ok(libs_import)
39✔
100
    }
39✔
101

102
    fn get_metadata(&self) -> Result<ProtoMetadata, GenerationError> {
39✔
103
        let import_libs: Vec<String> = self.libs_by_type()?;
39✔
104
        let metadata = ProtoMetadata {
39✔
105
            package_name: self.names.package_name.clone(),
39✔
106
            import_libs,
39✔
107
            lower_name: self.names.proto_file_stem.clone(),
39✔
108
            plural_pascal_name: self.names.plural_pascal_name.clone(),
39✔
109
            pascal_name: self.names.pascal_name.clone(),
39✔
110
            props: self.props(),
39✔
111
            version_field_id: self.schema.schema.fields.len() + 1,
39✔
112
            enable_token: self.schema.enable_token,
39✔
113
            enable_on_event: self.schema.enable_on_event,
39✔
114
        };
39✔
115
        Ok(metadata)
39✔
116
    }
39✔
117

118
    pub fn render_protos(&self) -> Result<Vec<NamedProto>, GenerationError> {
39✔
119
        let metadata = self.get_metadata()?;
39✔
120
        let mut protos = vec![];
39✔
121

39✔
122
        let types_proto = include_str!("../../../../../dozer-types/protos/types.proto");
39✔
123
        let resource_proto = self.handlebars.render("main", &metadata)?;
39✔
124

125
        protos.push(NamedProto {
39✔
126
            name: "types.proto".to_string(),
39✔
127
            content: types_proto.to_string(),
39✔
128
        });
39✔
129
        protos.push(NamedProto {
39✔
130
            name: self.names.proto_file_name.clone(),
39✔
131
            content: resource_proto,
39✔
132
        });
39✔
133

39✔
134
        Ok(protos)
39✔
135
    }
39✔
136

137
    pub fn generate_proto(&self, folder_path: &Path) -> Result<String, GenerationError> {
39✔
138
        if !folder_path.exists() {
39✔
139
            return Err(GenerationError::DirPathNotExist(folder_path.to_path_buf()));
×
140
        }
39✔
141
        let protos = self.render_protos()?;
39✔
142
        for NamedProto { name, content } in protos {
117✔
143
            let proto_path = folder_path.join(&name);
78✔
144
            std::fs::write(&proto_path, content)
78✔
145
                .map_err(|e| GenerationError::FailedToWriteToFile(proto_path, e))?;
78✔
146
        }
147

148
        Ok(self.names.proto_file_stem.clone())
39✔
149
    }
39✔
150

151
    pub fn read(
48✔
152
        descriptor: &DescriptorPool,
48✔
153
        schema_name: &str,
48✔
154
    ) -> Result<ServiceDesc, GenerationError> {
48✔
155
        fn get_field(
1,327✔
156
            message: &MessageDescriptor,
1,327✔
157
            field_name: &str,
1,327✔
158
        ) -> Result<FieldDescriptor, GenerationError> {
1,327✔
159
            message
1,327✔
160
                .get_field_by_name(field_name)
1,327✔
161
                .ok_or_else(|| GenerationError::FieldNotFound {
1,327✔
162
                    message_name: message.name().to_string(),
×
163
                    field_name: field_name.to_string(),
×
164
                })
1,327✔
165
        }
1,327✔
166

48✔
167
        let record_desc_from_message =
48✔
168
            |message: MessageDescriptor| -> Result<RecordDesc, GenerationError> {
94✔
169
                let version_field = get_field(&message, "__dozer_record_version")?;
94✔
170

171
                if let Some(point_values) = descriptor.get_message_by_name(POINT_TYPE_CLASS) {
94✔
172
                    let pv = point_values;
94✔
173
                    if let Some(decimal_values) = descriptor.get_message_by_name(DECIMAL_TYPE_CLASS)
94✔
174
                    {
175
                        if let Some(dv) = descriptor.get_message_by_name(DURATION_TYPE_CLASS) {
94✔
176
                            let durv = dv;
94✔
177
                            Ok(RecordDesc {
94✔
178
                                message,
94✔
179
                                version_field,
94✔
180
                                point_field: PointDesc {
94✔
181
                                    message: pv.clone(),
94✔
182
                                    x: get_field(&pv, "x")?,
94✔
183
                                    y: get_field(&pv, "y")?,
94✔
184
                                },
185
                                decimal_field: DecimalDesc {
186
                                    message: decimal_values.clone(),
94✔
187
                                    scale: get_field(&decimal_values, "scale")?,
94✔
188
                                    lo: get_field(&decimal_values, "lo")?,
94✔
189
                                    mid: get_field(&decimal_values, "mid")?,
94✔
190
                                    hi: get_field(&decimal_values, "hi")?,
94✔
191
                                    negative: get_field(&decimal_values, "negative")?,
94✔
192
                                },
193
                                duration_field: DurationDesc {
194
                                    message: durv.clone(),
94✔
195
                                    value: get_field(&durv, "value")?,
94✔
196
                                    time_unit: get_field(&durv, "time_unit")?,
94✔
197
                                },
198
                            })
199
                        } else {
200
                            Err(ServiceNotFound(DURATION_TYPE_CLASS.to_string()))
×
201
                        }
202
                    } else {
203
                        Err(ServiceNotFound(DECIMAL_TYPE_CLASS.to_string()))
×
204
                    }
205
                } else {
206
                    Err(ServiceNotFound(POINT_TYPE_CLASS.to_string()))
×
207
                }
208
            };
94✔
209

210
        let names = Names::new(schema_name, &Schema::default());
48✔
211
        let service_name = format!("{}.{}", &names.package_name, &names.plural_pascal_name);
48✔
212
        let service = descriptor
48✔
213
            .get_service_by_name(&service_name)
48✔
214
            .ok_or(GenerationError::ServiceNotFound(service_name))?;
48✔
215

216
        let mut count = None;
48✔
217
        let mut query = None;
48✔
218
        let mut on_event = None;
48✔
219
        let mut token = None;
48✔
220
        for method in service.methods() {
153✔
221
            match method.name() {
153✔
222
                "count" => {
153✔
223
                    let message = method.output();
48✔
224
                    let count_field = get_field(&message, "count")?;
48✔
225
                    count = Some(CountMethodDesc {
48✔
226
                        method,
48✔
227
                        response_desc: CountResponseDesc {
48✔
228
                            message,
48✔
229
                            count_field,
48✔
230
                        },
48✔
231
                    });
48✔
232
                }
233
                "query" => {
105✔
234
                    let message = method.output();
48✔
235
                    let records_field = get_field(&message, "records")?;
48✔
236
                    let records_filed_kind = records_field.kind();
48✔
237
                    let Kind::Message(record_with_id_message) = records_filed_kind else {
48✔
238
                        return Err(GenerationError::ExpectedMessageField {
×
239
                            filed_name: records_field.full_name().to_string(),
×
240
                            actual: records_filed_kind,
×
241
                        });
×
242
                    };
243
                    let id_field = get_field(&record_with_id_message, "id")?;
48✔
244
                    let record_field = get_field(&record_with_id_message, "record")?;
48✔
245
                    let record_field_kind = record_field.kind();
48✔
246
                    let Kind::Message(record_message) = record_field_kind else {
48✔
247
                        return Err(GenerationError::ExpectedMessageField {
×
248
                            filed_name: record_field.full_name().to_string(),
×
249
                            actual: record_field_kind,
×
250
                        });
×
251
                    };
252
                    query = Some(QueryMethodDesc {
48✔
253
                        method,
48✔
254
                        response_desc: QueryResponseDesc {
48✔
255
                            message,
48✔
256
                            records_field,
48✔
257
                            record_with_id_desc: RecordWithIdDesc {
48✔
258
                                message: record_with_id_message,
48✔
259
                                id_field,
48✔
260
                                record_field,
48✔
261
                                record_desc: record_desc_from_message(record_message)?,
48✔
262
                            },
263
                        },
264
                    });
265
                }
266
                "OnEvent" => {
57✔
267
                    let message = method.output();
46✔
268
                    let typ_field = get_field(&message, "typ")?;
46✔
269
                    let old_field = get_field(&message, "old")?;
46✔
270
                    let new_field = get_field(&message, "new")?;
46✔
271
                    let new_id_field = get_field(&message, "new_id")?;
46✔
272
                    let old_field_kind = old_field.kind();
46✔
273
                    let Kind::Message(record_message) = old_field_kind else {
46✔
274
                        return Err(GenerationError::ExpectedMessageField {
×
275
                            filed_name: old_field.full_name().to_string(),
×
276
                            actual: old_field_kind,
×
277
                        });
×
278
                    };
279
                    on_event = Some(OnEventMethodDesc {
46✔
280
                        method,
46✔
281
                        response_desc: EventDesc {
46✔
282
                            message,
46✔
283
                            typ_field,
46✔
284
                            old_field,
46✔
285
                            new_field,
46✔
286
                            new_id_field,
46✔
287
                            record_desc: record_desc_from_message(record_message)?,
46✔
288
                        },
289
                    });
290
                }
291
                "token" => {
11✔
292
                    let message = method.output();
11✔
293
                    let token_field = get_field(&message, "token")?;
11✔
294
                    token = Some(TokenMethodDesc {
11✔
295
                        method,
11✔
296
                        response_desc: TokenResponseDesc {
11✔
297
                            message,
11✔
298
                            token_field,
11✔
299
                        },
11✔
300
                    });
11✔
301
                }
302
                _ => {
303
                    return Err(GenerationError::UnexpectedMethod(
×
304
                        method.full_name().to_string(),
×
305
                    ))
×
306
                }
307
            }
308
        }
309

310
        let Some(count) = count else {
48✔
311
            return Err(GenerationError::MissingCountMethod(
×
312
                service.full_name().to_string(),
×
313
            ));
×
314
        };
315
        let Some(query) = query else {
48✔
316
            return Err(GenerationError::MissingQueryMethod(
×
317
                service.full_name().to_string(),
×
318
            ));
×
319
        };
320

321
        Ok(ServiceDesc {
48✔
322
            service,
48✔
323
            count,
48✔
324
            query,
48✔
325
            on_event,
48✔
326
            token,
48✔
327
        })
48✔
328
    }
48✔
329
}
330

331
struct Names {
332
    proto_file_name: String,
333
    package_name: String,
334
    proto_file_stem: String,
335
    plural_pascal_name: String,
336
    pascal_name: String,
337
    record_field_names: Vec<String>,
338
}
339

340
impl Names {
341
    fn new(schema_name: &str, schema: &Schema) -> Self {
87✔
342
        if schema_name.contains('-') {
87✔
343
            error!("Name of the endpoint should not contain `-`.");
×
344
        }
87✔
345
        let schema_name = schema_name.replace(|c: char| !c.is_ascii_alphanumeric(), "_");
663✔
346

87✔
347
        let package_name = format!("dozer.generated.{schema_name}");
87✔
348
        let proto_file_stem = schema_name.to_lowercase();
87✔
349
        let plural_pascal_name = schema_name.to_pascal_case().to_plural();
87✔
350
        let pascal_name = schema_name.to_pascal_case().to_singular();
87✔
351
        let record_field_names = schema
87✔
352
            .fields
87✔
353
            .iter()
87✔
354
            .map(|field| {
93✔
355
                if field.name.contains('-') {
93✔
356
                    error!("Name of the field should not contain `-`.");
×
357
                }
93✔
358
                field
93✔
359
                    .name
93✔
360
                    .replace(|c: char| !c.is_ascii_alphanumeric(), "_")
621✔
361
            })
93✔
362
            .collect::<Vec<_>>();
87✔
363
        Self {
87✔
364
            proto_file_name: format!("{proto_file_stem}.proto"),
87✔
365
            package_name,
87✔
366
            proto_file_stem,
87✔
367
            plural_pascal_name,
87✔
368
            pascal_name,
87✔
369
            record_field_names,
87✔
370
        }
87✔
371
    }
87✔
372
}
373

374
fn convert_dozer_type_to_proto_type(field_type: FieldType) -> Result<String, GenerationError> {
186✔
375
    match field_type {
186✔
376
        FieldType::UInt => Ok("uint64".to_owned()),
36✔
377
        FieldType::U128 => Ok("string".to_owned()),
×
378
        FieldType::Int => Ok("int64".to_owned()),
72✔
379
        FieldType::I128 => Ok("string".to_owned()),
×
380
        FieldType::Float => Ok("double".to_owned()),
18✔
381
        FieldType::Boolean => Ok("bool".to_owned()),
×
382
        FieldType::String => Ok("string".to_owned()),
54✔
383
        FieldType::Text => Ok("string".to_owned()),
×
384
        FieldType::Binary => Ok("bytes".to_owned()),
×
385
        FieldType::Decimal => Ok(DECIMAL_TYPE_CLASS.to_owned()),
×
386
        FieldType::Timestamp => Ok(TIMESTAMP_TYPE_CLASS.to_owned()),
6✔
387
        FieldType::Date => Ok("string".to_owned()),
×
388
        FieldType::Json => Ok(JSON_TYPE_CLASS.to_owned()),
×
389
        FieldType::Point => Ok(POINT_TYPE_CLASS.to_owned()),
×
390
        FieldType::Duration => Ok(DURATION_TYPE_CLASS.to_owned()),
×
391
    }
392
}
186✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc