reproto_semck/
lib.rs

1extern crate reproto_core as core;
2
3use self::Component::*;
4use self::Violation::*;
5use core::errors::*;
6use core::flavored::{RpChannel, RpDecl, RpEndpoint, RpField, RpFile, RpName, RpNamed, RpType,
7                     RpVariantRef};
8use core::{Loc, Span, Version};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
12pub enum Component {
13    Minor,
14    Patch,
15}
16
17impl Component {
18    /// Describe the component that was violated.
19    pub fn describe(&self) -> &str {
20        match *self {
21            Minor => "minor change violation",
22            Patch => "patch change violation",
23        }
24    }
25}
26
27#[derive(Debug)]
28pub enum Violation {
29    /// An entire declaration has been removed.
30    DeclRemoved(Component, Span),
31    /// An entire declaration has been added.
32    DeclAdded(Component, Span),
33    /// Field was removed.
34    RemoveField(Component, Span),
35    /// Variant was removed.
36    RemoveVariant(Component, Span),
37    /// Field added.
38    AddField(Component, Span),
39    /// Variant added.
40    AddVariant(Component, Span),
41    /// Field type was changed from one to another.
42    FieldTypeChange(Component, RpType, Span, RpType, Span),
43    /// Field name was changed from one to another.
44    FieldNameChange(Component, String, Span, String, Span),
45    /// Variant identifier was changed from one to another.
46    VariantOrdinalChange(Component, String, Span, String, Span),
47    /// Field made required.
48    FieldRequiredChange(Component, Span, Span),
49    /// Required field added.
50    AddRequiredField(Component, Span),
51    /// Field modifier changed.
52    FieldModifierChange(Component, Span, Span),
53    /// Endpoint added.
54    AddEndpoint(Component, Span),
55    /// Endpoint removed.
56    RemoveEndpoint(Component, Span),
57    /// Endpoint request type changed.
58    EndpointRequestChange(Component, Option<RpChannel>, Span, Option<RpChannel>, Span),
59    /// Endpoint response type changed.
60    EndpointResponseChange(Component, Option<RpChannel>, Span, Option<RpChannel>, Span),
61}
62
63fn fields<'a>(named: &RpNamed<'a>) -> Vec<&'a Loc<RpField>> {
64    use core::RpNamed::*;
65
66    match *named {
67        Type(target) => target.fields.iter().collect(),
68        Tuple(target) => target.fields.iter().collect(),
69        Interface(target) => target.fields.iter().collect(),
70        SubType(target) => target.fields.iter().collect(),
71        _ => vec![],
72    }
73}
74
75fn enum_variants<'a>(named: &'a RpNamed) -> Vec<RpVariantRef<'a>> {
76    use core::RpNamed::*;
77
78    match *named {
79        Enum(target) => target.variants.iter().collect(),
80        _ => vec![],
81    }
82}
83
84fn endpoints_to_map<'a>(named: &RpNamed<'a>) -> HashMap<&'a str, &'a Loc<RpEndpoint>> {
85    use core::RpNamed::*;
86
87    match *named {
88        Service(target) => target.endpoints.iter().map(|e| (e.ident(), e)).collect(),
89        _ => HashMap::new(),
90    }
91}
92
93fn decls_to_map<'a, I: 'a>(decls: I) -> HashMap<RpName, RpNamed<'a>>
94where
95    I: IntoIterator<Item = &'a RpDecl>,
96{
97    let mut storage = HashMap::new();
98
99    for decl in decls {
100        for named in decl.to_named() {
101            // Checked separately for each Enum.
102            if let core::RpNamed::EnumVariant(_) = named {
103                continue;
104            }
105
106            storage.insert(Loc::borrow(named.name()).clone().localize(), named);
107        }
108    }
109
110    storage
111}
112
113fn variants_to_map<'a, I: 'a>(variants: I) -> HashMap<RpName, RpVariantRef<'a>>
114where
115    I: IntoIterator<Item = RpVariantRef<'a>>,
116{
117    let mut storage = HashMap::new();
118
119    for variant in variants {
120        storage.insert(Loc::borrow(&variant.name).clone().localize(), variant);
121    }
122
123    storage
124}
125
126fn fields_to_map<'a, I: 'a>(fields: I) -> HashMap<String, &'a Loc<RpField>>
127where
128    I: IntoIterator<Item = &'a Loc<RpField>>,
129{
130    let mut storage = HashMap::new();
131
132    for field in fields {
133        storage.insert(field.ident().to_string(), field);
134    }
135
136    storage
137}
138
139/// Perform checks on an endpoint channel.
140fn check_endpoint_channel<F, E>(
141    component: Component,
142    violations: &mut Vec<Violation>,
143    from_endpoint: &Loc<RpEndpoint>,
144    to_endpoint: &Loc<RpEndpoint>,
145    accessor: F,
146    error: E,
147) -> Result<()>
148where
149    F: Fn(&RpEndpoint) -> &Option<Loc<RpChannel>>,
150    E: Fn(Component, Option<RpChannel>, Span, Option<RpChannel>, Span) -> Violation,
151{
152    let from_ty = accessor(from_endpoint)
153        .as_ref()
154        .map(|r| (r.is_streaming(), r.ty().clone().localize()));
155
156    let to_ty = accessor(to_endpoint)
157        .as_ref()
158        .map(|r| (r.is_streaming(), r.ty().clone().localize()));
159
160    if from_ty != to_ty {
161        let from_pos = accessor(from_endpoint)
162            .as_ref()
163            .map(|r| Loc::span(r))
164            .unwrap_or(Loc::span(from_endpoint));
165
166        let to_pos = accessor(to_endpoint)
167            .as_ref()
168            .map(|r| Loc::span(r))
169            .unwrap_or(Loc::span(to_endpoint));
170
171        violations.push(error(
172            component,
173            accessor(from_endpoint)
174                .as_ref()
175                .map(Loc::borrow)
176                .map(Clone::clone),
177            from_pos.into(),
178            accessor(to_endpoint)
179                .as_ref()
180                .map(Loc::borrow)
181                .map(Clone::clone),
182            to_pos.into(),
183        ));
184    }
185
186    Ok(())
187}
188
189fn check_endpoint_type(
190    component: Component,
191    violations: &mut Vec<Violation>,
192    from_endpoint: &Loc<RpEndpoint>,
193    to_endpoint: &Loc<RpEndpoint>,
194) -> Result<()> {
195    // TODO: check arguments.
196    /*check_endpoint_channel(
197        component.clone(),
198        violations,
199        from_endpoint,
200        to_endpoint,
201        |e| &e.request,
202        EndpointRequestChange,
203    )?;*/
204
205    check_endpoint_channel(
206        component.clone(),
207        violations,
208        from_endpoint,
209        to_endpoint,
210        |e| &e.response,
211        EndpointResponseChange,
212    )?;
213
214    Ok(())
215}
216
217fn common_check_variant(
218    component: Component,
219    violations: &mut Vec<Violation>,
220    from_variant: RpVariantRef,
221    to_variant: RpVariantRef,
222) -> Result<()> {
223    if from_variant.value != to_variant.value {
224        violations.push(VariantOrdinalChange(
225            component.clone(),
226            from_variant.to_string(),
227            from_variant.span.into(),
228            to_variant.to_string(),
229            to_variant.span.into(),
230        ));
231    }
232
233    Ok(())
234}
235
236fn common_check_field(
237    component: Component,
238    violations: &mut Vec<Violation>,
239    from_field: &Loc<RpField>,
240    to_field: &Loc<RpField>,
241) -> Result<()> {
242    if to_field.ty.clone().localize() != from_field.ty.clone().localize() {
243        violations.push(FieldTypeChange(
244            component.clone(),
245            from_field.ty.clone(),
246            Loc::span(from_field).into(),
247            to_field.ty.clone(),
248            Loc::span(to_field).into(),
249        ));
250    }
251
252    // not permitted to rename fields.
253    if to_field.name() != from_field.name() {
254        violations.push(FieldNameChange(
255            component.clone(),
256            from_field.name().to_string(),
257            Loc::span(from_field).into(),
258            to_field.name().to_string(),
259            Loc::span(to_field).into(),
260        ));
261    }
262
263    Ok(())
264}
265
266/// Performs checks for minor version violations.
267fn check_minor(from: &RpFile, to: &RpFile) -> Result<Vec<Violation>> {
268    let mut violations = Vec::new();
269
270    let from_storage = decls_to_map(&from.decls);
271    let mut to_storage = decls_to_map(&to.decls);
272
273    for (name, from_named) in from_storage {
274        if let Some(to_named) = to_storage.remove(&name) {
275            let from_fields = fields_to_map(fields(&from_named));
276            let mut to_fields = fields_to_map(fields(&to_named));
277
278            for (name, from_field) in from_fields.into_iter() {
279                if let Some(to_field) = to_fields.remove(&name) {
280                    check_field(&mut violations, from_field, to_field)?;
281                } else {
282                    violations.push(RemoveField(Minor, Loc::span(from_field).into()));
283                }
284            }
285
286            // check that added fields are not required.
287            for (_, to_field) in to_fields.into_iter() {
288                if to_field.is_required() {
289                    violations.push(AddRequiredField(Minor, Loc::span(to_field).into()));
290                }
291            }
292
293            let from_variants = variants_to_map(enum_variants(&from_named));
294            let mut to_variants = variants_to_map(enum_variants(&to_named));
295
296            for (name, from_variant) in from_variants.into_iter() {
297                if let Some(to_variant) = to_variants.remove(&name) {
298                    check_variant(&mut violations, from_variant, to_variant)?;
299                } else {
300                    violations.push(RemoveVariant(Minor, from_variant.span.into()));
301                }
302            }
303
304            let from_endpoints = endpoints_to_map(&from_named);
305            let mut to_endpoints = endpoints_to_map(&to_named);
306
307            for (name, from_endpoint) in from_endpoints.into_iter() {
308                if let Some(to_endpoint) = to_endpoints.remove(&name) {
309                    check_endpoint(&mut violations, from_endpoint, to_endpoint)?;
310                } else {
311                    violations.push(RemoveEndpoint(Minor, Loc::span(from_endpoint).into()));
312                }
313            }
314        } else {
315            violations.push(DeclRemoved(Minor, from_named.span().into()));
316        }
317    }
318
319    return Ok(violations);
320
321    fn check_field(
322        violations: &mut Vec<Violation>,
323        from_field: &Loc<RpField>,
324        to_field: &Loc<RpField>,
325    ) -> Result<()> {
326        common_check_field(Minor, violations, from_field, to_field)?;
327
328        // Minor patch may make fields optional, but not required.
329        if from_field.is_optional() && to_field.is_required() {
330            violations.push(FieldRequiredChange(
331                Minor,
332                Loc::span(from_field).into(),
333                Loc::span(to_field).into(),
334            ));
335        }
336
337        Ok(())
338    }
339
340    fn check_variant(
341        violations: &mut Vec<Violation>,
342        from_variant: RpVariantRef,
343        to_variant: RpVariantRef,
344    ) -> Result<()> {
345        common_check_variant(Minor, violations, from_variant, to_variant)?;
346        Ok(())
347    }
348
349    fn check_endpoint(
350        violations: &mut Vec<Violation>,
351        from_endpoint: &Loc<RpEndpoint>,
352        to_endpoint: &Loc<RpEndpoint>,
353    ) -> Result<()> {
354        check_endpoint_type(Minor, violations, from_endpoint, to_endpoint)?;
355        Ok(())
356    }
357}
358
359fn check_patch(from: &RpFile, to: &RpFile) -> Result<Vec<Violation>> {
360    let mut violations = Vec::new();
361
362    let from_storage = decls_to_map(&from.decls);
363    let mut to_storage = decls_to_map(&to.decls);
364
365    for (name, from_named) in from_storage {
366        if let Some(to_named) = to_storage.remove(&name) {
367            let from_fields = fields_to_map(fields(&from_named));
368            let mut to_fields = fields_to_map(fields(&to_named));
369
370            for (name, from_field) in from_fields.into_iter() {
371                if let Some(to_field) = to_fields.remove(&name) {
372                    check_field(&mut violations, from_field, to_field)?;
373                } else {
374                    violations.push(RemoveField(Patch, Loc::span(from_field).into()));
375                }
376            }
377
378            // added fields are not permitted
379            for (_, to_field) in to_fields.into_iter() {
380                violations.push(AddField(Patch, Loc::span(to_field).into()));
381            }
382
383            let from_variants = variants_to_map(enum_variants(&from_named));
384            let mut to_variants = variants_to_map(enum_variants(&to_named));
385
386            for (name, from_variant) in from_variants.into_iter() {
387                if let Some(to_variant) = to_variants.remove(&name) {
388                    check_variant(&mut violations, from_variant, to_variant)?;
389                } else {
390                    violations.push(RemoveVariant(Patch, from_variant.span.into()));
391                }
392            }
393
394            // added variants are not permitted
395            for (_, to_variant) in to_variants.into_iter() {
396                violations.push(AddVariant(Patch, to_variant.span.into()));
397            }
398
399            let from_endpoints = endpoints_to_map(&from_named);
400            let mut to_endpoints = endpoints_to_map(&to_named);
401
402            for (name, from_endpoint) in from_endpoints.into_iter() {
403                if let Some(to_endpoint) = to_endpoints.remove(&name) {
404                    check_endpoint(&mut violations, from_endpoint, to_endpoint)?;
405                } else {
406                    violations.push(RemoveEndpoint(Patch, Loc::span(from_endpoint).into()));
407                }
408            }
409
410            // added endpoints are not permitted
411            for (_, to_endpoint) in to_endpoints.into_iter() {
412                violations.push(AddEndpoint(Patch, Loc::span(to_endpoint).into()));
413            }
414        } else {
415            violations.push(DeclRemoved(Patch, from_named.span().into()));
416        }
417    }
418
419    for (_, to_named) in to_storage.into_iter() {
420        violations.push(DeclAdded(Patch, to_named.span().into()));
421    }
422
423    return Ok(violations);
424
425    fn check_field(
426        violations: &mut Vec<Violation>,
427        from_field: &Loc<RpField>,
428        to_field: &Loc<RpField>,
429    ) -> Result<()> {
430        common_check_field(Patch, violations, from_field, to_field)?;
431
432        if to_field.required != from_field.required {
433            violations.push(FieldModifierChange(
434                Patch,
435                Loc::span(from_field).into(),
436                Loc::span(to_field).into(),
437            ));
438        }
439
440        Ok(())
441    }
442
443    fn check_variant(
444        violations: &mut Vec<Violation>,
445        from_variant: RpVariantRef,
446        to_variant: RpVariantRef,
447    ) -> Result<()> {
448        common_check_variant(Patch, violations, from_variant, to_variant)?;
449        Ok(())
450    }
451
452    fn check_endpoint(
453        violations: &mut Vec<Violation>,
454        from_endpoint: &Loc<RpEndpoint>,
455        to_endpoint: &Loc<RpEndpoint>,
456    ) -> Result<()> {
457        check_endpoint_type(Patch, violations, from_endpoint, to_endpoint)?;
458        Ok(())
459    }
460}
461
462pub fn check(from: (&Version, &RpFile), to: (&Version, &RpFile)) -> Result<Vec<Violation>> {
463    let (from_version, from_file) = from;
464    let (to_version, to_file) = to;
465
466    if from_version.major == to_version.major {
467        if from_version.minor < to_version.minor {
468            return check_minor(from_file, to_file);
469        }
470
471        if from_version.patch < to_version.patch {
472            return check_patch(from_file, to_file);
473        }
474    }
475
476    Ok(vec![])
477}