tinc_build/
types.rs

1use std::collections::BTreeMap;
2use std::fmt::Write;
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use tinc_pb_prost::http_endpoint_options;
7
8use crate::codegen::cel::{CelExpression, CelExpressions};
9use crate::codegen::utils::{field_ident_from_str, get_common_import_path, type_ident_from_str};
10use crate::path_set::PathSet;
11use crate::{ExternPaths, Mode};
12
13#[derive(Debug, Clone, PartialEq)]
14pub(crate) enum ProtoType {
15    Value(ProtoValueType),
16    Modified(ProtoModifiedValueType),
17}
18
19impl ProtoType {
20    pub(crate) fn value_type(&self) -> Option<&ProtoValueType> {
21        match self {
22            Self::Value(value) => Some(value),
23            Self::Modified(modified) => modified.value_type(),
24        }
25    }
26
27    pub(crate) fn nested(&self) -> bool {
28        matches!(
29            self,
30            Self::Modified(ProtoModifiedValueType::Map(_, _) | ProtoModifiedValueType::Repeated(_))
31        )
32    }
33}
34
35#[derive(Debug, Clone, PartialEq)]
36pub(crate) enum ProtoValueType {
37    String,
38    Bytes,
39    Int32,
40    Int64,
41    UInt32,
42    UInt64,
43    Float,
44    Double,
45    Bool,
46    WellKnown(ProtoWellKnownType),
47    Message(ProtoPath),
48    Enum(ProtoPath),
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub(crate) enum ProtoWellKnownType {
53    Timestamp,
54    Duration,
55    Struct,
56    Value,
57    Empty,
58    ListValue,
59    Any,
60}
61
62impl ProtoValueType {
63    #[cfg(feature = "prost")]
64    pub(crate) fn from_pb(ty: &prost_reflect::Kind) -> Self {
65        match ty {
66            prost_reflect::Kind::Double => ProtoValueType::Double,
67            prost_reflect::Kind::Float => ProtoValueType::Float,
68            prost_reflect::Kind::Int32 => ProtoValueType::Int32,
69            prost_reflect::Kind::Int64 => ProtoValueType::Int64,
70            prost_reflect::Kind::Uint32 => ProtoValueType::UInt32,
71            prost_reflect::Kind::Uint64 => ProtoValueType::UInt64,
72            prost_reflect::Kind::Sint32 => ProtoValueType::Int32,
73            prost_reflect::Kind::Sint64 => ProtoValueType::Int64,
74            prost_reflect::Kind::Fixed32 => ProtoValueType::Float,
75            prost_reflect::Kind::Fixed64 => ProtoValueType::Double,
76            prost_reflect::Kind::Sfixed32 => ProtoValueType::Float,
77            prost_reflect::Kind::Sfixed64 => ProtoValueType::Double,
78            prost_reflect::Kind::Bool => ProtoValueType::Bool,
79            prost_reflect::Kind::String => ProtoValueType::String,
80            prost_reflect::Kind::Bytes => ProtoValueType::Bytes,
81            prost_reflect::Kind::Message(message) => ProtoValueType::from_proto_path(message.full_name()),
82            prost_reflect::Kind::Enum(enum_) => ProtoValueType::Enum(ProtoPath::new(enum_.full_name())),
83        }
84    }
85
86    pub(crate) fn from_proto_path(path: &str) -> Self {
87        match path {
88            "google.protobuf.Timestamp" => ProtoValueType::WellKnown(ProtoWellKnownType::Timestamp),
89            "google.protobuf.Duration" => ProtoValueType::WellKnown(ProtoWellKnownType::Duration),
90            "google.protobuf.Struct" => ProtoValueType::WellKnown(ProtoWellKnownType::Struct),
91            "google.protobuf.Value" => ProtoValueType::WellKnown(ProtoWellKnownType::Value),
92            "google.protobuf.Empty" => ProtoValueType::WellKnown(ProtoWellKnownType::Empty),
93            "google.protobuf.ListValue" => ProtoValueType::WellKnown(ProtoWellKnownType::ListValue),
94            "google.protobuf.Any" => ProtoValueType::WellKnown(ProtoWellKnownType::Any),
95            "google.protobuf.BoolValue" => ProtoValueType::Bool,
96            "google.protobuf.Int32Value" => ProtoValueType::Int32,
97            "google.protobuf.Int64Value" => ProtoValueType::Int64,
98            "google.protobuf.UInt32Value" => ProtoValueType::UInt32,
99            "google.protobuf.UInt64Value" => ProtoValueType::UInt64,
100            "google.protobuf.FloatValue" => ProtoValueType::Float,
101            "google.protobuf.DoubleValue" => ProtoValueType::Double,
102            "google.protobuf.StringValue" => ProtoValueType::String,
103            "google.protobuf.BytesValue" => ProtoValueType::Bytes,
104            _ => ProtoValueType::Message(ProtoPath::new(path)),
105        }
106    }
107
108    pub(crate) fn proto_path(&self) -> &str {
109        match self {
110            ProtoValueType::WellKnown(ProtoWellKnownType::Timestamp) => "google.protobuf.Timestamp",
111            ProtoValueType::WellKnown(ProtoWellKnownType::Duration) => "google.protobuf.Duration",
112            ProtoValueType::WellKnown(ProtoWellKnownType::Struct) => "google.protobuf.Struct",
113            ProtoValueType::WellKnown(ProtoWellKnownType::Value) => "google.protobuf.Value",
114            ProtoValueType::WellKnown(ProtoWellKnownType::Empty) => "google.protobuf.Empty",
115            ProtoValueType::WellKnown(ProtoWellKnownType::ListValue) => "google.protobuf.ListValue",
116            ProtoValueType::WellKnown(ProtoWellKnownType::Any) => "google.protobuf.Any",
117            ProtoValueType::Bool => "google.protobuf.BoolValue",
118            ProtoValueType::Int32 => "google.protobuf.Int32Value",
119            ProtoValueType::Int64 => "google.protobuf.Int64Value",
120            ProtoValueType::UInt32 => "google.protobuf.UInt32Value",
121            ProtoValueType::UInt64 => "google.protobuf.UInt64Value",
122            ProtoValueType::Float => "google.protobuf.FloatValue",
123            ProtoValueType::Double => "google.protobuf.DoubleValue",
124            ProtoValueType::String => "google.protobuf.StringValue",
125            ProtoValueType::Bytes => "google.protobuf.BytesValue",
126            ProtoValueType::Enum(path) | ProtoValueType::Message(path) => path.as_ref(),
127        }
128    }
129}
130
131#[derive(Debug, Clone, PartialEq)]
132pub(crate) struct ProtoEnumType {
133    pub package: ProtoPath,
134    pub full_name: ProtoPath,
135    pub comments: Comments,
136    pub options: ProtoEnumOptions,
137    pub variants: IndexMap<String, ProtoEnumVariant>,
138}
139
140impl ProtoEnumType {
141    fn rust_path(&self, package: &str) -> syn::Path {
142        get_common_import_path(package, &self.full_name)
143    }
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub(crate) struct ProtoEnumOptions {
148    pub repr_enum: bool,
149}
150
151#[derive(Debug, Clone, PartialEq)]
152pub(crate) struct ProtoEnumVariant {
153    pub full_name: ProtoPath,
154    pub comments: Comments,
155    pub options: ProtoEnumVariantOptions,
156    pub rust_ident: syn::Ident,
157    pub value: i32,
158}
159
160#[derive(Debug, Clone, PartialEq)]
161pub(crate) struct ProtoEnumVariantOptions {
162    pub serde_name: String,
163    pub visibility: ProtoVisibility,
164}
165
166#[derive(Debug, Clone, PartialEq)]
167pub(crate) enum ProtoModifiedValueType {
168    Repeated(ProtoValueType),
169    Map(ProtoValueType, ProtoValueType),
170    Optional(ProtoValueType),
171    OneOf(ProtoOneOfType),
172}
173
174impl ProtoModifiedValueType {
175    pub(crate) fn value_type(&self) -> Option<&ProtoValueType> {
176        match self {
177            Self::Repeated(v) => Some(v),
178            Self::Map(_, v) => Some(v),
179            Self::Optional(v) => Some(v),
180            _ => None,
181        }
182    }
183}
184
185#[derive(Debug, Clone, PartialEq)]
186pub(crate) struct ProtoMessageType {
187    pub package: ProtoPath,
188    pub full_name: ProtoPath,
189    pub comments: Comments,
190    pub options: ProtoMessageOptions,
191    pub fields: IndexMap<String, ProtoMessageField>,
192}
193
194impl ProtoMessageType {
195    fn rust_path(&self, package: &str) -> syn::Path {
196        get_common_import_path(package, &self.full_name)
197    }
198}
199
200#[derive(Debug, Clone, PartialEq, Default)]
201pub(crate) struct ProtoMessageOptions {
202    pub cel: Vec<CelExpression>,
203}
204
205#[derive(Debug, Clone, PartialEq)]
206pub(crate) struct ProtoMessageField {
207    pub full_name: ProtoPath,
208    pub message: ProtoPath,
209    pub ty: ProtoType,
210    pub comments: Comments,
211    pub options: ProtoFieldOptions,
212}
213
214impl ProtoMessageField {
215    pub(crate) fn rust_ident(&self) -> syn::Ident {
216        field_ident_from_str(self.full_name.split('.').next_back().unwrap())
217    }
218}
219
220#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
221pub(crate) enum ProtoFieldSerdeOmittable {
222    True,
223    False,
224    TrueButStillSerialize,
225}
226
227impl ProtoFieldSerdeOmittable {
228    pub(crate) fn is_true(&self) -> bool {
229        matches!(self, Self::True | Self::TrueButStillSerialize)
230    }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq)]
234pub(crate) enum ProtoVisibility {
235    Default,
236    Skip,
237    InputOnly,
238    OutputOnly,
239}
240
241impl ProtoVisibility {
242    pub(crate) fn from_pb(visibility: tinc_pb_prost::Visibility) -> Self {
243        match visibility {
244            tinc_pb_prost::Visibility::Skip => ProtoVisibility::Skip,
245            tinc_pb_prost::Visibility::InputOnly => ProtoVisibility::InputOnly,
246            tinc_pb_prost::Visibility::OutputOnly => ProtoVisibility::OutputOnly,
247            tinc_pb_prost::Visibility::Unspecified => ProtoVisibility::Default,
248        }
249    }
250
251    pub(crate) fn has_output(&self) -> bool {
252        matches!(self, ProtoVisibility::OutputOnly | ProtoVisibility::Default)
253    }
254
255    pub(crate) fn has_input(&self) -> bool {
256        matches!(self, ProtoVisibility::InputOnly | ProtoVisibility::Default)
257    }
258}
259
260#[derive(Debug, Clone, PartialEq)]
261pub(crate) struct ProtoFieldOptions {
262    pub serde_name: String,
263    pub serde_omittable: ProtoFieldSerdeOmittable,
264    pub nullable: bool,
265    pub flatten: bool,
266    pub visibility: ProtoVisibility,
267    pub cel_exprs: CelExpressions,
268}
269
270#[derive(Debug, Clone, PartialEq)]
271pub(crate) struct ProtoOneOfType {
272    pub full_name: ProtoPath,
273    pub message: ProtoPath,
274    pub options: ProtoOneOfOptions,
275    pub fields: IndexMap<String, ProtoOneOfField>,
276}
277
278impl ProtoOneOfType {
279    pub(crate) fn rust_path(&self, package: &str) -> syn::Path {
280        get_common_import_path(package, &self.full_name)
281    }
282}
283
284#[derive(Debug, Clone, PartialEq)]
285pub(crate) struct ProtoOneOfOptions {
286    pub tagged: Option<Tagged>,
287}
288
289#[derive(Debug, Clone, PartialEq)]
290pub(crate) struct Tagged {
291    pub tag: String,
292    pub content: String,
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub(crate) struct ProtoOneOfField {
297    pub full_name: ProtoPath,
298    pub message: ProtoPath,
299    pub comments: Comments,
300    pub ty: ProtoValueType,
301    pub options: ProtoFieldOptions,
302}
303
304impl ProtoOneOfField {
305    pub(crate) fn rust_ident(&self) -> syn::Ident {
306        type_ident_from_str(self.full_name.split('.').next_back().unwrap())
307    }
308}
309
310#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
311pub(crate) struct ProtoPath(pub Arc<str>);
312
313impl ProtoPath {
314    pub(crate) fn trim_last_segment(&self) -> &str {
315        // remove the last .<segment> from the path
316        let (item, _) = self.0.rsplit_once('.').unwrap_or_default();
317        item
318    }
319}
320
321impl std::ops::Deref for ProtoPath {
322    type Target = str;
323
324    fn deref(&self) -> &Self::Target {
325        &self.0
326    }
327}
328
329impl AsRef<str> for ProtoPath {
330    fn as_ref(&self) -> &str {
331        &self.0
332    }
333}
334
335impl ProtoPath {
336    pub(crate) fn new(absolute: impl std::fmt::Display) -> Self {
337        Self(absolute.to_string().into())
338    }
339}
340
341impl PartialEq<&str> for ProtoPath {
342    fn eq(&self, other: &&str) -> bool {
343        &*self.0 == *other
344    }
345}
346
347impl PartialEq<str> for ProtoPath {
348    fn eq(&self, other: &str) -> bool {
349        &*self.0 == other
350    }
351}
352
353impl std::borrow::Borrow<str> for ProtoPath {
354    fn borrow(&self) -> &str {
355        &self.0
356    }
357}
358
359impl std::fmt::Display for ProtoPath {
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        f.write_str(&self.0)
362    }
363}
364
365#[derive(Debug, Clone, PartialEq)]
366pub(crate) struct ProtoService {
367    pub full_name: ProtoPath,
368    pub package: ProtoPath,
369    pub comments: Comments,
370    pub options: ProtoServiceOptions,
371    pub methods: IndexMap<String, ProtoServiceMethod>,
372}
373
374#[derive(Debug, Clone, PartialEq)]
375pub(crate) struct ProtoServiceOptions {
376    pub prefix: Option<String>,
377}
378
379#[derive(Debug, Clone, PartialEq)]
380pub(crate) enum ProtoServiceMethodIo {
381    Single(ProtoValueType),
382    Stream(ProtoValueType),
383}
384
385impl ProtoServiceMethodIo {
386    pub(crate) fn is_stream(&self) -> bool {
387        matches!(self, ProtoServiceMethodIo::Stream(_))
388    }
389
390    pub(crate) fn value_type(&self) -> &ProtoValueType {
391        match self {
392            ProtoServiceMethodIo::Single(ty) => ty,
393            ProtoServiceMethodIo::Stream(ty) => ty,
394        }
395    }
396}
397
398#[derive(Debug, Clone, PartialEq)]
399pub(crate) struct ProtoServiceMethod {
400    pub full_name: ProtoPath,
401    pub service: ProtoPath,
402    pub comments: Comments,
403    pub input: ProtoServiceMethodIo,
404    pub output: ProtoServiceMethodIo,
405    pub endpoints: Vec<ProtoServiceMethodEndpoint>,
406    pub cel: Vec<CelExpression>,
407}
408
409#[derive(Debug, Clone, PartialEq, Default)]
410pub(crate) struct Comments {
411    pub leading: Option<Arc<str>>,
412    pub detached: Arc<[Arc<str>]>,
413    pub trailing: Option<Arc<str>>,
414}
415
416impl Comments {
417    pub(crate) fn is_empty(&self) -> bool {
418        self.leading.is_none() && self.detached.is_empty() && self.trailing.is_none()
419    }
420}
421
422impl std::fmt::Display for Comments {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424        let mut newline = false;
425        if let Some(leading) = self.leading.as_ref() {
426            leading.trim().fmt(f)?;
427            newline = true;
428        }
429
430        for detached in self.detached.iter() {
431            if newline {
432                f.write_char('\n')?;
433            }
434            newline = true;
435            detached.trim().fmt(f)?;
436        }
437
438        if let Some(detached) = self.trailing.as_ref() {
439            if newline {
440                f.write_char('\n')?;
441            }
442            detached.trim().fmt(f)?;
443        }
444
445        Ok(())
446    }
447}
448
449#[derive(Debug, Clone, PartialEq)]
450pub(crate) struct ProtoServiceMethodEndpoint {
451    pub method: http_endpoint_options::Method,
452    pub request: Option<http_endpoint_options::Request>,
453    pub response: Option<http_endpoint_options::Response>,
454}
455
456#[derive(Debug, Clone)]
457pub(crate) struct ProtoTypeRegistry {
458    messages: BTreeMap<ProtoPath, ProtoMessageType>,
459    enums: BTreeMap<ProtoPath, ProtoEnumType>,
460    services: BTreeMap<ProtoPath, ProtoService>,
461    extern_paths: ExternPaths,
462    floats_with_non_finite_vals: PathSet,
463    _mode: Mode,
464}
465
466impl ProtoTypeRegistry {
467    pub(crate) fn new(mode: Mode, extern_paths: ExternPaths, floats_with_non_finite_vals: PathSet) -> Self {
468        Self {
469            messages: BTreeMap::new(),
470            enums: BTreeMap::new(),
471            services: BTreeMap::new(),
472            extern_paths,
473            floats_with_non_finite_vals,
474            _mode: mode,
475        }
476    }
477
478    pub(crate) fn register_message(&mut self, message: ProtoMessageType) {
479        self.messages.insert(message.full_name.clone(), message);
480    }
481
482    pub(crate) fn register_enum(&mut self, enum_: ProtoEnumType) {
483        self.enums.insert(enum_.full_name.clone(), enum_);
484    }
485
486    pub(crate) fn register_service(&mut self, service: ProtoService) {
487        self.services.insert(service.full_name.clone(), service);
488    }
489
490    pub(crate) fn get_message(&self, full_name: &str) -> Option<&ProtoMessageType> {
491        self.messages.get(full_name)
492    }
493
494    pub(crate) fn get_enum(&self, full_name: &str) -> Option<&ProtoEnumType> {
495        self.enums.get(full_name)
496    }
497
498    pub(crate) fn get_service(&self, full_name: &str) -> Option<&ProtoService> {
499        self.services.get(full_name)
500    }
501
502    pub(crate) fn messages(&self) -> impl Iterator<Item = &ProtoMessageType> {
503        self.messages.values()
504    }
505
506    pub(crate) fn enums(&self) -> impl Iterator<Item = &ProtoEnumType> {
507        self.enums.values()
508    }
509
510    pub(crate) fn services(&self) -> impl Iterator<Item = &ProtoService> {
511        self.services.values()
512    }
513
514    pub(crate) fn resolve_rust_path(&self, package: &str, path: &str) -> Option<syn::Path> {
515        self.extern_paths
516            .resolve(path)
517            .or_else(|| Some(self.enums.get(path)?.rust_path(package)))
518            .or_else(|| Some(self.messages.get(path)?.rust_path(package)))
519    }
520
521    pub(crate) fn has_extern(&self, path: &str) -> bool {
522        self.extern_paths.contains(path)
523    }
524
525    pub(crate) fn support_non_finite_vals(&self, path: &ProtoPath) -> bool {
526        self.floats_with_non_finite_vals.contains(path)
527    }
528}