rgbstd/stl/
mime.rs

1// RGB standard library for working with smart contracts on Bitcoin & Lightning
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22#![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    /// # Safety
56    ///
57    /// Panics is the provided string is an invalid type specifier.
58    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
131// TODO: Ensure all constructors filter invalid characters
132impl 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}