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 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 DeclRemoved(Component, Span),
31 DeclAdded(Component, Span),
33 RemoveField(Component, Span),
35 RemoveVariant(Component, Span),
37 AddField(Component, Span),
39 AddVariant(Component, Span),
41 FieldTypeChange(Component, RpType, Span, RpType, Span),
43 FieldNameChange(Component, String, Span, String, Span),
45 VariantOrdinalChange(Component, String, Span, String, Span),
47 FieldRequiredChange(Component, Span, Span),
49 AddRequiredField(Component, Span),
51 FieldModifierChange(Component, Span, Span),
53 AddEndpoint(Component, Span),
55 RemoveEndpoint(Component, Span),
57 EndpointRequestChange(Component, Option<RpChannel>, Span, Option<RpChannel>, Span),
59 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 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
139fn 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 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 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
266fn 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 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 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 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 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 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}