winget_types/shared/
value.rs

1use std::{fmt, fmt::Display, marker::PhantomData, str::FromStr};
2
3use compact_str::CompactString;
4use derive_more::{Deref, Display};
5use serde::Serialize;
6use serde_with::DeserializeFromStr;
7use thiserror::Error;
8
9use crate::{
10    installer::{
11        Command, FileExtension, InstallModes, InstallerReturnCode, Protocol, UpgradeBehavior,
12        switches::{CustomSwitch, SilentSwitch, SilentWithProgressSwitch},
13    },
14    locale::{
15        Author, Copyright, Description, InstallationNotes, License, Moniker, PackageName,
16        Publisher, ShortDescription, Tag,
17    },
18    shared::{
19        language_tag::LanguageTag,
20        package_identifier::PackageIdentifier,
21        package_version::PackageVersion,
22        url::{
23            CopyrightUrl, LicenseUrl, PackageUrl, PublisherSupportUrl, PublisherUrl,
24            ReleaseNotesUrl,
25        },
26    },
27};
28
29#[derive(
30    Clone,
31    Debug,
32    Default,
33    Deref,
34    Display,
35    Eq,
36    PartialEq,
37    Ord,
38    PartialOrd,
39    Hash,
40    Serialize,
41    DeserializeFromStr,
42)]
43#[display("{_0}")]
44pub struct Value<const MIN: usize, const MAX: usize>(CompactString);
45
46#[derive(Error, Debug, Eq, PartialEq)]
47pub enum ValueError<T: ValueName, const MIN: usize, const MAX: usize> {
48    TooLong,
49    TooShort,
50    Phantom(PhantomData<T>),
51}
52
53impl<T: ValueName, const MIN: usize, const MAX: usize> Display for ValueError<T, MIN, MAX> {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::TooLong => {
57                write!(f, "{} cannot be more than {MAX} characters long", T::NAME)
58            }
59            Self::TooShort => {
60                if MIN == 1 {
61                    write!(f, "{} cannot be empty", T::NAME)
62                } else {
63                    write!(f, "{} must be at least {MIN} characters long", T::NAME)
64                }
65            }
66            Self::Phantom(_) => unreachable!(),
67        }
68    }
69}
70
71impl<const MIN: usize, const MAX: usize> Value<MIN, MAX> {
72    #[expect(clippy::missing_errors_doc)]
73    pub fn new<T: ValueName, S: Into<CompactString>>(
74        value: S,
75    ) -> Result<Self, ValueError<T, MIN, MAX>> {
76        let value = value.into();
77        match value.chars().count() {
78            count if count < MIN => Err(ValueError::TooShort),
79            count if count > MAX => Err(ValueError::TooLong),
80            _ => Ok(Self(value)),
81        }
82    }
83}
84
85impl<const MIN: usize, const MAX: usize> FromStr for Value<MIN, MAX> {
86    type Err = ValueError<Self, MIN, MAX>;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        Self::new::<Self, _>(s)
90    }
91}
92
93impl<const MIN: usize, const MAX: usize> ValueName for Value<MIN, MAX> {
94    const NAME: &'static str = "Value";
95}
96
97#[macro_export]
98macro_rules! winget_value {
99    ($name:ident, $min:expr, $max:expr) => {
100        #[derive(
101            Clone,
102            Debug,
103            Default,
104            derive_more::Deref,
105            derive_more::Display,
106            Eq,
107            PartialEq,
108            derive_more::FromStr,
109            Ord,
110            PartialOrd,
111            Hash,
112            serde::Serialize,
113            serde_with::DeserializeFromStr,
114        )]
115        pub struct $name($crate::shared::value::Value<$min, $max>);
116
117        impl $name {
118            #[allow(unused)]
119            const MIN_CHAR_LENGTH: usize = $min;
120
121            #[allow(unused)]
122            const MAX_CHAR_LENGTH: usize = $max;
123
124            #[expect(clippy::missing_errors_doc)]
125            pub fn new<S: Into<compact_str::CompactString>>(
126                value: S,
127            ) -> Result<Self, $crate::shared::value::ValueError<Self, $min, $max>> {
128                $crate::shared::value::Value::new(value).map(Self)
129            }
130        }
131    };
132}
133
134pub trait ValueName {
135    const NAME: &'static str;
136}
137
138macro_rules! value_name {
139    ($( $name:ty => $name_str:literal ),*$(,)?) => {
140        $(
141            impl ValueName for $name {
142                const NAME: &'static str = $name_str;
143            }
144        )*
145    };
146}
147
148value_name!(
149    Author => "Author",
150    Command => "Command",
151    Copyright => "Copyright",
152    CopyrightUrl => "Copyright URL",
153    CustomSwitch => "Custom switch",
154    Description => "Description",
155    FileExtension => "File extension",
156    InstallationNotes => "Installation notes",
157    InstallerReturnCode => "Installer return code",
158    InstallModes => "Install modes",
159    LanguageTag => "Language tag",
160    License => "License",
161    LicenseUrl => "License URL",
162    Moniker => "Moniker",
163    PackageIdentifier => "Package identifier",
164    PackageName => "Package name",
165    PackageUrl => "Package URL",
166    PackageVersion => "Package version",
167    Protocol => "Protocol",
168    Publisher => "Publisher",
169    PublisherSupportUrl => "Publisher support URL",
170    PublisherUrl => "Publisher URL",
171    ReleaseNotesUrl => "Release notes URL",
172    ShortDescription => "Short description",
173    SilentSwitch => "Silent switch",
174    SilentWithProgressSwitch => "Silent with progress switch",
175    Tag => "Tag",
176    UpgradeBehavior => "Upgrade behavior",
177);