1use std::fmt::Display;
2
3use semver::Version;
4use tracing::{info, instrument, trace};
5
6use crate::{
7 CargoFile, ReadToml, current_span,
8 manifest::error::{ItemType, VersionlocationError},
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum VersionType {
13 Package,
14 SetByWorkspace,
15 WorkspacePackage,
16}
17
18#[derive(Debug, Clone, Copy)]
19pub enum VersionLocation {
20 Package,
21 WorkspacePackage,
22}
23
24impl Display for VersionLocation {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(
27 f,
28 "{}",
29 match self {
30 VersionLocation::Package => "package.version",
31 VersionLocation::WorkspacePackage => "workspace.package.version",
32 }
33 )
34 }
35}
36
37impl VersionLocation {
38 #[track_caller]
39 #[instrument(skip_all, fields(version, path))]
40 pub fn get_version(
41 &self,
42 cargo_toml: &CargoFile<ReadToml>,
43 ) -> Result<Version, VersionlocationError> {
44 use crate::manifest::error::VersionLocationErrorKind as ErrKind;
45 let path = cargo_toml.path();
46
47 let set_err = |kind: ErrKind, context: Option<&'static str>| kind.to_error(path, context);
48 let _span = current_span!();
49 let document = cargo_toml
50 .contents()
51 .expect("Can't call this function without the document read.");
52 tracing::trace!("have document");
53 let ret = match self {
54 VersionLocation::Package => {
55 let package = document
56 .get("package")
57 .ok_or(ErrKind::PackageNotFound.to_error(path, Some("First check")))?;
58
59 let package_table = package.as_table().ok_or_else(|| {
60 set_err(
61 ErrKind::ItemInvalid(package.into()),
62 Some("Tried to get package table"),
63 )
64 })?;
65
66 match package_table.get("version").ok_or(set_err(
67 ErrKind::NotFound(*self),
68 Some("Package table located."),
69 ))? {
70 toml_edit::Item::Value(value) => {
71 Version::parse(value.as_str().unwrap()).map_err(|e| set_err(e.into(), None))
72 }
73 toml_edit::Item::Table(table) => {
74 let workspace_table = table.get("workspace").ok_or(set_err(
75 ErrKind::ItemInvalid(ItemType::Table),
76 Some("expected version.workspace = <bool>"),
77 ))?;
78 let val = workspace_table.as_bool().ok_or(set_err(
79 ErrKind::ItemInvalid(ItemType::Value),
80 Some("Expected bool"),
81 ))?;
82
83 let msg = format!("in the manifest file: version.workspace = {val}");
84 Err(ErrKind::SetByWorkspace.to_error(path, Some(msg)))
85 }
86
87 item => Err(set_err(ErrKind::ItemInvalid(item.into()), None)),
88 }
89 }
90 VersionLocation::WorkspacePackage => {
91 let workspace =
92 document
93 .get("workspace")
94 .ok_or(ErrKind::WorkspaceNotFound.to_error(
95 path,
96 Some(format!(
97 "Workspace Package: {}",
98 path.as_os_str().to_str().unwrap()
99 )),
100 ))?;
101
102 let workspace = workspace.as_table().ok_or_else(|| {
103 set_err(
104 ErrKind::ItemInvalid(workspace.into()),
105 Some("Tried to get package table"),
106 )
107 })?;
108 let package = workspace
109 .get("package")
110 .ok_or(ErrKind::PackageNotFound.to_error(path, Some("")))?;
111
112 let package = package.as_table().ok_or_else(|| {
113 set_err(
114 ErrKind::ItemInvalid(package.into()),
115 Some("Tried to get package table"),
116 )
117 })?;
118 match package.get("version").ok_or(set_err(
119 ErrKind::NotFound(*self),
120 Some("Package table located."),
121 ))? {
122 toml_edit::Item::Value(value) => Version::parse(value.as_str().unwrap())
123 .map_err(|e| set_err(e.into(), Some("Workspace Version"))),
124 item => Err(set_err(ErrKind::ItemInvalid(item.into()), None)),
125 }
126 }
127 };
128 let version = ret?;
129 current_span!().record("path", path.as_os_str().to_str().unwrap_or_default());
130 current_span!().record("version", &version.to_string());
131 info!("Version found");
132
133 Ok(version)
134 }
135
136 #[track_caller]
137 #[instrument(skip_all, fields(version, path))]
138 pub fn set_version<'a>(
139 &self,
140 cargo_toml: &mut CargoFile<ReadToml>,
141 version: &Version,
142 ) -> Result<(), VersionlocationError> {
143 use crate::manifest::error::VersionLocationErrorKind as ErrKind;
144 let path = cargo_toml.path().to_path_buf();
145
146 let set_err = |kind: ErrKind, context: Option<&'static str>| kind.to_error(&path, context);
147
148 let _span = current_span!();
149
150 let document = cargo_toml
151 .contents_mut()
152 .expect("Can't call this function without the document read.");
153
154 trace!("have document");
155
156 let ret = match self {
157 VersionLocation::Package => {
158 let package = document
159 .get_mut("package")
160 .ok_or(set_err(ErrKind::PackageNotFound, None))?;
161 let pack_kind = ItemType::from(&*package);
162
163 let package_table = package.as_table_mut().ok_or_else(|| {
164 set_err(
165 ErrKind::ItemInvalid(pack_kind),
166 Some("Tried to get package table"),
167 )
168 })?;
169
170 match package_table.get_mut("version").ok_or(set_err(
171 ErrKind::NotFound(*self),
172 Some("Package table located."),
173 ))? {
174 toml_edit::Item::Value(value) => Ok(*value = version.to_string().into()),
175 item => Err(set_err(
176 ErrKind::ItemInvalid(item.into()),
177 Some("Invalid itemtype for setting package version."),
178 )),
179 }
180 }
181 VersionLocation::WorkspacePackage => {
182 let workspace = document
183 .get_mut("workspace")
184 .ok_or(set_err(ErrKind::WorkspaceNotFound, None))?;
185
186 let ws_kind = ItemType::from(&*workspace);
187 let workspace = workspace.as_table_mut().ok_or_else(|| {
188 set_err(
189 ErrKind::ItemInvalid(ws_kind.into()),
190 Some("Tried to get package table"),
191 )
192 })?;
193 let package = workspace
194 .get_mut("package")
195 .ok_or(set_err(ErrKind::PackageNotFound, None))?;
196 let pack_kind = ItemType::from(&*package);
197
198 let package = package.as_table_mut().ok_or_else(|| {
199 set_err(
200 ErrKind::ItemInvalid(pack_kind.into()),
201 Some("Tried to get package table"),
202 )
203 })?;
204 match package.get_mut("version").ok_or(set_err(
205 ErrKind::NotFound(*self),
206 Some("Package table located."),
207 ))? {
208 toml_edit::Item::Value(value) => Ok(*value = version.to_string().into()),
209 item => Err(set_err(
210 ErrKind::ItemInvalid(item.into()),
211 Some("Invalid itemtype for setting workspace version."),
212 )),
213 }
214 }
215 };
216 let _version = ret?;
217 current_span!().record("path", (&path).as_os_str().to_str().unwrap_or_default());
218 info!("Version set: {version}");
219 Ok(())
220 }
221}