cargo_next/
lib.rs

1use semver::Version;
2use std::{fs, io::Error as IoError, path::Path};
3use thiserror::Error;
4use toml_edit::{value, Document, Item, TomlError};
5
6/// The error type of this crate.
7#[derive(Debug, Error)]
8pub enum Error {
9    /// An error that occurred during the read and write operation of the
10    /// `Cargo.toml` file.
11    #[error("an io error occurred")]
12    IoError(#[from] IoError),
13    /// An error that occures while parsing a semver version string.
14    #[error("An error occurred during version parsing")]
15    SemverParseError(#[from] semver::Error),
16    /// An error that occurred during the toml parsing.
17    #[error("a parser error occurred")]
18    ParseError(#[from] TomlError),
19    /// An error that gets emitted if the `package.version` field has not the
20    /// right type (String).
21    #[error("the field {field:?} is not of type {ty:?}")]
22    InvalidFieldType { field: String, ty: String },
23}
24
25/// An enum defining what types of increments can be done to a semver version.
26#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
27pub enum SemVer {
28    /// A major increment.
29    Major,
30    /// A minor increment.
31    Minor,
32    /// A patch increment.
33    Patch,
34}
35
36/// Returns the version inside a `Cargo.toml` file.
37///
38/// # Arguments
39///
40/// - `path`: The path to the `Cargo.toml` file.
41///
42/// # Returns
43///
44/// The version as a `String` if it could be successfully extracted, otherwise
45/// an error.
46pub fn get_version(path: impl AsRef<Path>) -> Result<Version, Error> {
47    let cargo_toml_content = fs::read_to_string(path.as_ref())?;
48    let doc = cargo_toml_content.parse::<Document>()?;
49    let item: &Item = &doc["package"]["version"];
50
51    // This should be the case for valid Cargo.toml files.
52    if let Some(s) = item.as_str() {
53        Ok(Version::parse(s)?)
54    } else {
55        Err(Error::InvalidFieldType {
56            field: "version".to_string(),
57            ty: "string".to_string(),
58        })
59    }
60}
61
62/// Sets the version inside a `Cargo.toml` file.
63///
64/// # Arguments
65///
66/// - `path`: The path to the `Cargo.toml` file.
67/// - `version`: The version to write into the file. Note that no checks are
68///   done to see whether the value contains a valid semver version.
69///
70/// # Returns
71///
72/// An error if something went wrong during IO operations or parsing.
73pub fn set_version(path: impl AsRef<Path>, version: impl AsRef<str>) -> Result<(), Error> {
74    let cargo_toml_content = fs::read_to_string(path.as_ref())?;
75    let mut doc = cargo_toml_content.parse::<Document>()?;
76
77    doc["package"]["version"] = value(version.as_ref());
78    fs::write(path.as_ref(), doc.to_string())?;
79
80    Ok(())
81}
82
83/// Bumps the version inside a `Cargo.toml` file according to semver specs.
84///
85/// # Arguments
86///
87/// - `path`: The path to the `Cargo.toml` file.
88/// - `type`: The type of bump. Either patch, minor or major.
89///
90/// # Returns
91///
92/// The new version or an error if something went wrong during IO operations.
93pub fn bump_version(path: impl AsRef<Path>, r#type: SemVer) -> Result<Version, Error> {
94    let mut version = get_version(path.as_ref())?;
95    match r#type {
96        SemVer::Major => version.increment_major(),
97        SemVer::Minor => version.increment_minor(),
98        SemVer::Patch => version.increment_patch(),
99    }
100
101    set_version(path, &version.to_string())?;
102    Ok(version)
103}
104
105trait SemVerExt {
106    fn increment_major(&mut self);
107    fn increment_minor(&mut self);
108    fn increment_patch(&mut self);
109}
110
111impl SemVerExt for Version {
112    fn increment_major(&mut self) {
113        self.major += 1;
114    }
115
116    fn increment_minor(&mut self) {
117        self.minor += 1;
118    }
119
120    fn increment_patch(&mut self) {
121        self.patch += 1;
122    }
123}