ark-api-ffi 0.17.0-pre.15

Ark low-level Wasm FFI API
Documentation
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Semver {
    major: u8,
    minor: u8,
    patch: u8,
    pre: u8,
}

impl Semver {
    pub const fn pack(self) -> u64 {
        (self.major as u64) << 24
            | (self.minor as u64) << 16
            | (self.patch as u64) << 8
            | (self.pre as u64)
    }

    pub const fn unpack(v: u64) -> Self {
        Self {
            major: (v >> 24) as u8,
            minor: (v >> 16) as u8,
            patch: (v >> 8) as u8,
            pre: v as u8,
        }
    }

    #[allow(clippy::should_implement_trait)] // probably should implement proper `from_str´ trait, but don't care about explicit errors
    pub fn from_str(s: &str) -> Option<Self> {
        if let Some((main, pre)) = s.split_once('-') {
            let mut main_it = main.split('.');
            let major: u8 = main_it.next()?.parse().ok()?;
            let minor: u8 = main_it.next()?.parse().ok()?;
            let patch: u8 = main_it.next()?.parse().ok()?;
            if main_it.next().is_some() {
                return None;
            }

            let (pre_text, pre_num) = pre.split_once('.')?;
            if pre_text != "pre" {
                return None;
            }
            let pre: u8 = pre_num.parse().ok()?;

            Some(Self {
                major,
                minor,
                patch,
                pre,
            })
        } else {
            let mut it = s.split('.');
            let major: u8 = it.next()?.parse().ok()?;
            let minor: u8 = it.next()?.parse().ok()?;
            let patch: u8 = it.next()?.parse().ok()?;
            if it.next().is_some() {
                return None;
            }

            Some(Self {
                major,
                minor,
                patch,
                pre: 0,
            })
        }
    }
}

impl std::cmp::PartialOrd for Semver {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl std::cmp::Ord for Semver {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        let major_cmp = self.major.cmp(&other.major);
        let minor_cmp = self.minor.cmp(&other.minor);
        let patch_cmp = self.patch.cmp(&other.patch);
        let pre_cmp = self.pre.cmp(&other.pre);

        if major_cmp != std::cmp::Ordering::Equal {
            return major_cmp;
        }
        if minor_cmp != std::cmp::Ordering::Equal {
            return minor_cmp;
        }
        if patch_cmp != std::cmp::Ordering::Equal {
            return patch_cmp;
        }

        if pre_cmp == std::cmp::Ordering::Equal {
            std::cmp::Ordering::Equal
        }
        // pre-releases count as less/older than non-pre releases
        else if self.pre == 0 {
            std::cmp::Ordering::Greater
        } else if other.pre == 0 {
            std::cmp::Ordering::Less
        } else {
            pre_cmp
        }
    }
}

impl std::fmt::Display for Semver {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
        if self.pre > 0 {
            write!(f, "-pre.{}", self.pre)?;
        }
        Ok(())
    }
}

#[no_mangle]
#[used]
#[doc(hidden)]
pub static ARK_PACKED_FFI_CRATE_VERSION: u64 = Semver {
    major: 0,
    minor: 17,
    patch: 0,
    pre: 15,
}
.pack();

#[cfg(test)]
#[allow(clippy::panic)]
mod test {
    use super::*;

    #[test]
    fn current_version_match() {
        let version = Semver::unpack(ARK_PACKED_FFI_CRATE_VERSION);
        let version_str = version.to_string();

        if version_str != env!("CARGO_PKG_VERSION") {
            panic!("Crate version mismatch, please update ARK_PACKED_FFI_CRATE_VERSION (\"{version_str}\") to be the same as the crate version in `api/api-ffi/Cargo.toml` (\"{}\")", env!("CARGO_PKG_VERSION"));
        }
    }

    #[test]
    fn compare() {
        assert_eq!(
            Semver {
                major: 0,
                minor: 2,
                patch: 3,
                pre: 4,
            },
            Semver {
                major: 0,
                minor: 2,
                patch: 3,
                pre: 4,
            }
        );

        assert!(
            Semver {
                major: 0,
                minor: 2,
                patch: 2,
                pre: 0,
            } >= Semver {
                major: 0,
                minor: 2,
                patch: 2,
                pre: 0,
            }
        );

        assert!(
            Semver {
                major: 0,
                minor: 2,
                patch: 2,
                pre: 0,
            } > Semver {
                major: 0,
                minor: 1,
                patch: 3,
                pre: 4,
            }
        );

        assert!(
            Semver {
                major: 0,
                minor: 2,
                patch: 3,
                pre: 0,
            } > Semver {
                major: 0,
                minor: 2,
                patch: 3,
                pre: 1,
            }
        );

        assert!(
            Semver {
                major: 5,
                minor: 0,
                patch: 0,
                pre: 1,
            } < Semver {
                major: 5,
                minor: 0,
                patch: 0,
                pre: 0,
            }
        );

        assert!(
            Semver {
                major: 3,
                minor: 4,
                patch: 5,
                pre: 6,
            } < Semver {
                major: 3,
                minor: 4,
                patch: 5,
                pre: 8,
            }
        );

        assert!(
            Semver {
                major: 3,
                minor: 4,
                patch: 5,
                pre: 6,
            } > Semver {
                major: 3,
                minor: 4,
                patch: 5,
                pre: 4,
            }
        );

        assert!(
            Semver {
                major: 0,
                minor: 2,
                patch: 2,
                pre: 0,
            } > Semver {
                major: 0,
                minor: 2,
                patch: 2,
                pre: 4,
            },
            "Pre-release should count as less / older than non-pre-release"
        );
    }

    #[test]
    fn string_valid() {
        for example in [
            Semver {
                major: 1,
                minor: 2,
                patch: 3,
                pre: 4,
            },
            Semver {
                major: 0,
                minor: 12,
                patch: 1,
                pre: 0,
            },
            Semver::unpack(ARK_PACKED_FFI_CRATE_VERSION),
        ] {
            let target = example.to_string();
            let converted_string =
                if let Some(converted_back) = Semver::from_str(&example.to_string()) {
                    converted_back.to_string()
                } else {
                    panic!("Failed to convert {example:?} / {target:?} to string");
                };

            assert_eq!(
                target, converted_string,
                "Mismatch in semver {example:?} string conversion. Had: {target:?}, got: {converted_string:?}"
            );
        }
    }

    #[test]
    fn string_invalid() {
        for example in ["", "0.0.", "1", "0.3.1.", "0.3.1.alpha1", "0.3.1-pre"] {
            assert!(
                Semver::from_str(example).is_none(),
                "Succeeded converting {example:?}, even though that should have failed"
            );
        }
    }
}