twitch_types/
lib.rs

1#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
2#![allow(clippy::extra_unused_lifetimes)]
3#![cfg_attr(nightly, feature(doc_cfg))]
4//! Twitch types
5
6#[macro_use]
7#[doc(hidden)]
8pub mod macros;
9mod collection;
10
11pub use collection::{Collection, CollectionIter};
12
13/// Convert a type into a [`Cow`](std::borrow::Cow)
14pub trait IntoCow<'a, Ref: ?Sized>
15where Ref: ToOwned {
16    /// Make the cow with proper ownership, muu
17    fn into_cow(self) -> std::borrow::Cow<'a, Ref>
18    where &'a Self: 'a;
19}
20
21impl<'a, R, S> IntoCow<'a, R> for std::borrow::Cow<'a, S>
22where
23    R: ToOwned + ?Sized + 'a,
24    S: ToOwned + ?Sized + 'a,
25    S::Owned: Into<R::Owned>,
26    &'a R: From<&'a S>,
27{
28    fn into_cow(self) -> std::borrow::Cow<'a, R> {
29        match self {
30            std::borrow::Cow::Borrowed(b) => std::borrow::Cow::Borrowed(b.into()),
31            std::borrow::Cow::Owned(o) => std::borrow::Cow::Owned(o.into()),
32        }
33    }
34}
35
36impl<'a, R> IntoCow<'a, R> for &'a str
37where
38    &'a str: Into<&'a R>,
39    R: ToOwned + ?Sized + 'a,
40{
41    fn into_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Borrowed(self.into()) }
42}
43
44impl<'a, R> IntoCow<'a, R> for &'a String
45where
46    &'a String: Into<&'a R>,
47    R: ToOwned + ?Sized + 'a,
48{
49    fn into_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Borrowed(self.into()) }
50}
51
52impl<'a, R> IntoCow<'a, R> for String
53where
54    String: Into<R::Owned>,
55    R: ToOwned + ?Sized + 'a,
56{
57    fn into_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Owned(self.into()) }
58}
59
60mod basic;
61// cc: https://github.com/rust-lang/rust/issues/83428, can't use glob imports and keep the modules private
62#[cfg(feature = "chat")]
63/// types for chat
64pub mod chat;
65#[cfg(feature = "color")]
66/// types for colors
67pub mod color;
68#[cfg(feature = "emote")]
69/// types for emotes
70pub mod emote;
71#[cfg(feature = "entitlement")]
72/// types for entitlements
73pub mod entitlement;
74#[cfg(feature = "eventsub")]
75/// types for eventsub related things
76pub mod eventsub;
77#[cfg(feature = "extension")]
78/// types for extensions
79pub mod extension;
80#[cfg(feature = "goal")]
81/// types for goals
82pub mod goal;
83#[cfg(feature = "moderation")]
84/// types for moderation
85pub mod moderation;
86#[cfg(feature = "points")]
87/// types for points
88pub mod points;
89#[cfg(feature = "stream")]
90/// types for stream related things
91pub mod stream;
92#[cfg(feature = "sub")]
93/// types for subscriptions
94pub mod sub;
95#[cfg(feature = "timestamp")]
96/// types for time
97pub mod time;
98#[cfg(feature = "user")]
99/// types for user related things
100pub mod user;
101
102pub use basic::*;
103
104#[cfg(feature = "chat")]
105pub use crate::chat::*;
106#[cfg(feature = "color")]
107pub use crate::color::*;
108#[cfg(feature = "emote")]
109pub use crate::emote::*;
110#[cfg(feature = "entitlement")]
111pub use crate::entitlement::*;
112#[cfg(feature = "eventsub")]
113pub use crate::eventsub::*;
114#[cfg(feature = "extension")]
115pub use crate::extension::*;
116#[cfg(feature = "goal")]
117pub use crate::goal::*;
118#[cfg(feature = "moderation")]
119pub use crate::moderation::*;
120#[cfg(feature = "points")]
121pub use crate::points::*;
122#[cfg(feature = "stream")]
123pub use crate::stream::*;
124#[cfg(feature = "sub")]
125pub use crate::sub::*;
126#[cfg(feature = "timestamp")]
127pub use crate::time::*;
128#[cfg(feature = "user")]
129pub use crate::user::*;
130
131#[cfg(all(feature = "serde", feature = "stream"))]
132fn deserialize_none_from_empty_string<'de, D, S>(deserializer: D) -> Result<Option<S>, D::Error>
133where
134    D: serde::Deserializer<'de>,
135    S: serde::Deserialize<'de>, {
136    use serde::de::IntoDeserializer;
137    struct Inner<S>(std::marker::PhantomData<S>);
138    impl<'de, S> serde::de::Visitor<'de> for Inner<S>
139    where S: serde::Deserialize<'de>
140    {
141        type Value = Option<S>;
142
143        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144            formatter.write_str("any string")
145        }
146
147        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
148        where E: serde::de::Error {
149            match value {
150                "" => Ok(None),
151                v => S::deserialize(v.into_deserializer()).map(Some),
152            }
153        }
154
155        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
156        where E: serde::de::Error {
157            match &*value {
158                "" => Ok(None),
159                v => S::deserialize(v.into_deserializer()).map(Some),
160            }
161        }
162
163        fn visit_unit<E>(self) -> Result<Self::Value, E>
164        where E: serde::de::Error {
165            Ok(None)
166        }
167    }
168
169    deserializer.deserialize_any(Inner(std::marker::PhantomData))
170}
171
172#[cfg(test)]
173mod tests {
174    #![allow(clippy::needless_borrow)]
175    use super::*;
176
177    #[test]
178    #[allow(clippy::needless_borrows_for_generic_args)]
179    fn lol() {
180        assert!(broadcaster_id("literal"));
181        assert!(!broadcaster_id(String::from("string")));
182        assert!(broadcaster_id(&String::from("ref string")));
183        assert!(broadcaster_id(UserIdRef::from_static("static ref")));
184        assert!(!broadcaster_id(UserId::new(String::from("owned"))));
185        assert!(broadcaster_id(&UserId::new(String::from("borrowed owned"))));
186        assert!(broadcaster_id(&*UserId::new(String::from("deref owned"))));
187        assert!(!broadcaster_id(std::borrow::Cow::Owned::<'_, UserIdRef>(
188            UserId::new(String::from("cow owned"))
189        )));
190        assert!(broadcaster_id(std::borrow::Cow::Borrowed(
191            UserIdRef::from_static("cow borrowed")
192        )));
193
194        assert!(broadcaster_id(opt(Some(std::borrow::Cow::Borrowed(
195            "through fn borrow"
196        )))));
197
198        assert!(!broadcaster_id(opt(Some(std::borrow::Cow::Owned(
199            "through fn owned".to_owned()
200        )))));
201
202        assert!(!broadcaster_id(opt_ref(Some(&std::borrow::Cow::Owned(
203            "through fn ref owned".to_owned()
204        )))));
205
206        assert!(broadcaster_id(opt_ref(Some(&std::borrow::Cow::Borrowed(
207            "through fn ref borrowed"
208        )))));
209    }
210
211    fn opt(cow: Option<std::borrow::Cow<'_, str>>) -> std::borrow::Cow<'_, UserIdRef> {
212        cow.map(|c| c.into_cow()).unwrap()
213    }
214    fn opt_ref<'a>(cow: Option<&std::borrow::Cow<'a, str>>) -> std::borrow::Cow<'a, UserIdRef> {
215        cow.map(|c| c.clone().into_cow()).unwrap()
216    }
217    /// aa
218    pub fn broadcaster_id<'a>(broadcaster_id: impl IntoCow<'a, UserIdRef> + 'a) -> bool {
219        struct K<'a> {
220            id: std::borrow::Cow<'a, UserIdRef>,
221        }
222        let k = K {
223            id: broadcaster_id.into_cow(),
224        };
225        matches!(k.id, std::borrow::Cow::Borrowed(_))
226    }
227
228    #[test]
229    fn debug_output_shown() {
230        let uid = UserIdRef::from_static("my-user-id");
231        let owned_uid = uid.to_owned();
232
233        assert_eq!(format!("{uid:?}"), "\"my-user-id\"");
234        assert_eq!(format!("{owned_uid:?}"), "\"my-user-id\"");
235    }
236
237    #[test]
238    #[cfg(feature = "stream")]
239    fn debug_output_hidden() {
240        let key = StreamKey::from_static("my-stream-key");
241        let owned_key = key.to_owned();
242
243        assert_eq!(format!("{key:?}"), "[redacted stream key]");
244        assert_eq!(format!("{owned_key:?}"), "[redacted stream key]");
245    }
246}