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);