xtask/
release_versions_manifest.rs1use super::{Component, Manifest, VersionFile, VersionKind};
2use anyhow::{bail, Result};
3use std::collections::HashSet;
4
5pub(super) fn validate_manifest(manifest: &Manifest) -> Result<()> {
6 let mut component_ids = HashSet::new();
7 let mut tag_prefixes: Vec<&str> = Vec::new();
8 for component in &manifest.components {
9 if component.id.trim().is_empty() {
10 bail!("release manifest contains an empty component id");
11 }
12 if !component_ids.insert(component.id.as_str()) {
13 bail!("duplicate release component id {}", component.id);
14 }
15 if component.tag_prefix.trim().is_empty() {
16 bail!("{} has an empty tag_prefix", component.id);
17 }
18 if tag_prefixes.iter().any(|existing| {
19 existing.starts_with(&component.tag_prefix)
20 || component.tag_prefix.starts_with(*existing)
21 }) {
22 bail!("{} tag_prefix overlaps another component", component.id);
23 }
24 tag_prefixes.push(&component.tag_prefix);
25 if component.shipping_paths.is_empty() {
26 bail!("{} has no shipping_paths", component.id);
27 }
28 if !component.release_workflow.ends_with(".yml")
29 && !component.release_workflow.ends_with(".yaml")
30 {
31 bail!("{} release_workflow must be a YAML workflow", component.id);
32 }
33 validate_version_file(component, "version_source", &component.version_source)?;
34 for file in &component.version_files {
35 validate_version_file(component, "version_files", file)?;
36 }
37 if !component
38 .version_files
39 .iter()
40 .any(|file| same_version_file(file, &component.version_source))
41 {
42 bail!(
43 "{} version_source is not listed in version_files",
44 component.id
45 );
46 }
47 }
48 Ok(())
49}
50
51fn validate_version_file(component: &Component, field: &str, file: &VersionFile) -> Result<()> {
52 match file.kind {
53 VersionKind::CargoPackage | VersionKind::CargoLockPackage => {
54 if file.package.as_deref().unwrap_or("").trim().is_empty() {
55 bail!(
56 "{} {field} {} {:?} requires package",
57 component.id,
58 file.path,
59 file.kind
60 );
61 }
62 if file.json_pointer.is_some() {
63 bail!(
64 "{} {field} {} {:?} must not set json_pointer",
65 component.id,
66 file.path,
67 file.kind
68 );
69 }
70 }
71 VersionKind::JsonVersion
72 | VersionKind::OciIdentifierVersion
73 | VersionKind::NpmIdentifierVersion => {
74 if file.package.is_some() {
75 bail!(
76 "{} {field} {} {:?} must not set package",
77 component.id,
78 file.path,
79 file.kind
80 );
81 }
82 let pointer = file.json_pointer.as_deref().unwrap_or("");
83 if !pointer.starts_with('/') {
84 bail!(
85 "{} {field} {} {:?} requires an absolute json_pointer",
86 component.id,
87 file.path,
88 file.kind
89 );
90 }
91 }
92 VersionKind::ChangelogHeading | VersionKind::JsonNoVersion => {
93 if file.package.is_some() || file.json_pointer.is_some() {
94 bail!(
95 "{} {field} {} {:?} must not set package/json_pointer",
96 component.id,
97 file.path,
98 file.kind
99 );
100 }
101 }
102 }
103 Ok(())
104}
105
106fn same_version_file(left: &VersionFile, right: &VersionFile) -> bool {
107 left.kind == right.kind
108 && left.path == right.path
109 && left.package == right.package
110 && left.json_pointer == right.json_pointer
111}