1#![allow(unused_braces)]
23
24use std::fmt::{self, Debug, Formatter};
25use std::str::FromStr;
26
27use amplify::ascii::AsciiString;
28use amplify::confinement::{Confined, NonEmptyVec};
29use amplify::s;
30use strict_encoding::{
31 InvalidIdent, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, TypedWrite,
32};
33use strict_types::StrictVal;
34
35use super::LIB_NAME_RGB_CONTRACT;
36
37#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash)]
38#[derive(StrictType, StrictEncode, StrictDecode)]
39#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
41pub struct MediaType {
42 #[strict_type(rename = "type")]
43 #[cfg_attr(feature = "serde", serde(rename = "type"))]
44 pub ty: MediaRegName,
45 pub subtype: Option<MediaRegName>,
46 pub charset: Option<MediaRegName>,
47}
48impl StrictDumb for MediaType {
49 fn strict_dumb() -> Self { MediaType::with("text/plain") }
50}
51impl StrictSerialize for MediaType {}
52impl StrictDeserialize for MediaType {}
53
54impl MediaType {
55 pub fn with(s: &'static str) -> Self {
59 let (ty, subty) = s.split_once('/').expect("invalid static media type string");
60 MediaType {
61 ty: MediaRegName::from(ty),
62 subtype: if subty == "*" {
63 None
64 } else {
65 Some(MediaRegName::from(subty))
66 },
67 charset: None,
68 }
69 }
70
71 pub fn from_strict_val_unchecked(value: &StrictVal) -> Self {
72 let ty = MediaRegName::from_strict_val_unchecked(value.unwrap_struct("type"));
73 let subtype = value
74 .unwrap_struct("subtype")
75 .unwrap_option()
76 .map(MediaRegName::from_strict_val_unchecked);
77 let charset = value
78 .unwrap_struct("charset")
79 .unwrap_option()
80 .map(MediaRegName::from_strict_val_unchecked);
81 Self {
82 ty,
83 subtype,
84 charset,
85 }
86 }
87}
88
89impl std::fmt::Display for MediaType {
90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 write!(
92 f,
93 "{}/{}",
94 self.ty,
95 if let Some(subty) = &self.subtype {
96 subty.to_string()
97 } else {
98 s!("*")
99 }
100 )
101 }
102}
103
104#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From)]
105#[wrapper(Deref, Display)]
106#[derive(StrictType, StrictDumb, StrictDecode)]
107#[strict_type(lib = LIB_NAME_RGB_CONTRACT, dumb = { MediaRegName::from("dumb") })]
108#[cfg_attr(
109 feature = "serde",
110 derive(Serialize, Deserialize),
111 serde(crate = "serde_crate", transparent)
112)]
113pub struct MediaRegName(Confined<AsciiString, 1, 64>);
114impl StrictEncode for MediaRegName {
115 fn strict_encode<W: TypedWrite>(&self, writer: W) -> std::io::Result<W> {
116 let iter = self
117 .0
118 .as_bytes()
119 .iter()
120 .map(|c| MimeChar::try_from(*c).unwrap());
121 writer.write_newtype::<Self>(&NonEmptyVec::<MimeChar, 64>::try_from_iter(iter).unwrap())
122 }
123}
124
125impl MediaRegName {
126 pub fn from_strict_val_unchecked(value: &StrictVal) -> Self {
127 MediaRegName::from_str(&value.unwrap_string()).expect("invalid media reg name")
128 }
129}
130
131impl FromStr for MediaRegName {
133 type Err = InvalidIdent;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 let s = AsciiString::from_ascii(s.as_bytes())?;
137 let s = Confined::try_from_iter(s.chars())?;
138 Ok(Self(s))
139 }
140}
141
142impl From<&'static str> for MediaRegName {
143 fn from(s: &'static str) -> Self { Self::from_str(s).expect("invalid media-reg name") }
144}
145
146impl TryFrom<String> for MediaRegName {
147 type Error = InvalidIdent;
148
149 fn try_from(name: String) -> Result<Self, InvalidIdent> {
150 let name = AsciiString::from_ascii(name.as_bytes())?;
151 let s = Confined::try_from(name)?;
152 Ok(Self(s))
153 }
154}
155
156impl Debug for MediaRegName {
157 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
158 f.debug_tuple("MediaRegName").field(&self.as_str()).finish()
159 }
160}
161
162#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
163#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
164#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)]
165#[display(inner)]
166#[repr(u8)]
167#[allow(non_camel_case_types)]
168pub enum MimeChar {
169 #[display("!")]
170 Excl = b'!',
171 #[display("#")]
172 Hash = b'#',
173 #[display("$")]
174 Dollar = b'$',
175 #[display("&")]
176 Amp = b'&',
177 #[display("+")]
178 Plus = b'+',
179 #[display("-")]
180 Dash = b'-',
181 #[display(".")]
182 Dot = b'.',
183 #[display("0")]
184 Zero = b'0',
185 #[display("1")]
186 One = b'1',
187 #[display("2")]
188 Two = b'2',
189 #[display("3")]
190 Three = b'3',
191 #[display("4")]
192 Four = b'4',
193 #[display("5")]
194 Five = b'5',
195 #[display("6")]
196 Six = b'6',
197 #[display("7")]
198 Seven = b'7',
199 #[display("8")]
200 Eight = b'8',
201 #[display("9")]
202 Nine = b'9',
203 #[display("^")]
204 Caret = b'^',
205 #[display("_")]
206 Lodash = b'_',
207 #[strict_type(dumb)]
208 #[display("a")]
209 a = b'a',
210 #[display("b")]
211 b = b'b',
212 #[display("c")]
213 c = b'c',
214 #[display("d")]
215 d = b'd',
216 #[display("e")]
217 e = b'e',
218 #[display("f")]
219 f = b'f',
220 #[display("g")]
221 g = b'g',
222 #[display("h")]
223 h = b'h',
224 #[display("i")]
225 i = b'i',
226 #[display("j")]
227 j = b'j',
228 #[display("k")]
229 k = b'k',
230 #[display("l")]
231 l = b'l',
232 #[display("m")]
233 m = b'm',
234 #[display("n")]
235 n = b'n',
236 #[display("o")]
237 o = b'o',
238 #[display("p")]
239 p = b'p',
240 #[display("q")]
241 q = b'q',
242 #[display("r")]
243 r = b'r',
244 #[display("s")]
245 s = b's',
246 #[display("t")]
247 t = b't',
248 #[display("u")]
249 u = b'u',
250 #[display("v")]
251 v = b'v',
252 #[display("w")]
253 w = b'w',
254 #[display("x")]
255 x = b'x',
256 #[display("y")]
257 y = b'y',
258 #[display("z")]
259 z = b'z',
260}