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