diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d209f52..9236ce65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,19 @@ ## [Unreleased] - ReleaseDate -[Commits](https://github.com/twitch-rs/twitch_types/compare/v0.3.5...Unreleased) +[Commits](https://github.com/twitch-rs/twitch_types/compare/v0.3.6...Unreleased) + +## [v0.3.6] - 2022-10-28 + +[Commits](https://github.com/twitch-rs/twitch_types/compare/v0.3.5...v0.3.6) + +### Added + +* Added `IntoCow` trait to easily take braids to be converted into `Cow`s + +### Changed + +* Added `impl From<&'a Owned> for &'a Ref` and `impl<'a> From<&'a Owned> for Cow<'a, Ref>` for all braids ## [v0.3.5] - 2022-10-22 diff --git a/Cargo.lock b/Cargo.lock index 5371f5b7..34cf447c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,7 +495,7 @@ dependencies = [ [[package]] name = "twitch_types" -version = "0.3.5" +version = "0.3.6" dependencies = [ "aliri_braid", "displaydoc", diff --git a/Cargo.toml b/Cargo.toml index 7ffc8fef..b635c096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "twitch_types" -version = "0.3.5" +version = "0.3.6" edition = "2021" repository = "https://github.com/twitch-rs/twitch_types" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 29978e42..5a0d3e21 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Twitch Types | Rust library for common types used in Twitch ============================================ -[![github]](https://github.com/twitch-rs/twitch_types) [![crates-io]](https://crates.io/crates/twitch_types) [![docs-rs-big]](https://docs.rs/twitch_types/0.3.5/twitch_types) +[![github]](https://github.com/twitch-rs/twitch_types) [![crates-io]](https://crates.io/crates/twitch_types) [![docs-rs-big]](https://docs.rs/twitch_types/0.3.6/twitch_types) [github]: https://img.shields.io/badge/github-twitch--rs/twitch__types-8da0cb?style=for-the-badge&labelColor=555555&logo=github [crates-io]: https://img.shields.io/crates/v/twitch_types.svg?style=for-the-badge&color=fc8d62&logo=rust diff --git a/release.toml b/release.toml index e8c74ead..dd9e90ba 100644 --- a/release.toml +++ b/release.toml @@ -1,9 +1,9 @@ -dev-version = false pre-release-commit-message = "release {{crate_name}} {{version}}" tag = false push = false publish = false enable-features = ["all", "twitch_oauth2/all", "unsupported"] +consolidate-commits = false pre-release-replacements = [ {file="CHANGELOG.md", search="Unreleased", replace="v{{version}}", prerelease=false}, {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", prerelease=false}, diff --git a/src/basic.rs b/src/basic.rs index d5e10140..f95bb09c 100644 --- a/src/basic.rs +++ b/src/basic.rs @@ -2,14 +2,20 @@ #[aliri_braid::braid(serde)] pub struct UserId; +impl_extra!(UserId, UserIdRef); + /// A users display name #[aliri_braid::braid(serde)] pub struct DisplayName; +impl_extra!(DisplayName, DisplayNameRef); + /// A nickname, not capitalized. #[aliri_braid::braid(serde)] pub struct Nickname; +impl_extra!(Nickname, NicknameRef); + /// A username, also specified as login. Should not be capitalized. pub type UserName = Nickname; diff --git a/src/color.rs b/src/color.rs index eba9ec79..fe8aff05 100644 --- a/src/color.rs +++ b/src/color.rs @@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize}; #[aliri_braid::braid(serde)] pub struct HexColor; +impl_extra!(HexColor, HexColorRef); + /// Colors a user can have #[derive(Debug, PartialEq, Eq, Deserialize, Clone)] #[serde(field_identifier, rename_all = "snake_case")] diff --git a/src/emote.rs b/src/emote.rs index f54eccf8..d047f49a 100644 --- a/src/emote.rs +++ b/src/emote.rs @@ -4,14 +4,20 @@ use serde::{Deserialize, Serialize}; #[aliri_braid::braid(serde)] pub struct BadgeSetId; +impl_extra!(BadgeSetId, BadgeSetIdRef); + /// A channel chat badge ID #[aliri_braid::braid(serde)] pub struct ChatBadgeId; +impl_extra!(ChatBadgeId, ChatBadgeIdRef); + /// A chat Emote ID #[aliri_braid::braid(serde)] pub struct EmoteId; +impl_extra!(EmoteId, EmoteIdRef); + impl EmoteIdRef { /// Generates url for this emote. /// @@ -214,6 +220,8 @@ impl EmoteUrlBuilder<'_> { #[aliri_braid::braid(serde)] pub struct EmoteSetId; +impl_extra!(EmoteSetId, EmoteSetIdRef); + /// An emote index as defined by eventsub, similar to IRC `emotes` twitch tag. #[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)] #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] diff --git a/src/eventsub.rs b/src/eventsub.rs index 320d8f14..d4bbce7a 100644 --- a/src/eventsub.rs +++ b/src/eventsub.rs @@ -1,3 +1,5 @@ /// An EventSub Subscription ID #[aliri_braid::braid(serde)] pub struct EventSubId; + +impl_extra!(EventSubId, EventSubIdRef); diff --git a/src/goal.rs b/src/goal.rs index 0f35f841..1a7e012b 100644 --- a/src/goal.rs +++ b/src/goal.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; #[aliri_braid::braid(serde)] pub struct CreatorGoalId; +impl_extra!(CreatorGoalId, CreatorGoalIdRef); + /// Type of creator goal #[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "snake_case")] diff --git a/src/lib.rs b/src/lib.rs index 8afc5675..9c4ac1e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,114 @@ #![cfg_attr(nightly, feature(doc_auto_cfg))] //! Twitch types +macro_rules! impl_extra { + (validated, $owned:path, $ref:path, $error:path) => { + impl<'a> TryFrom<&'a String> for &'a $ref { + type Error = $error; + fn try_from(string: &'a String) -> Result { + <$ref>::from_str(string.as_str()) + } + } + + impl_extra!(@all, $owned, $ref); + }; + ($owned:path, $ref:path) => { + impl<'a> From<&'a String> for &'a $ref { + fn from(string: &'a String) -> Self { + <$ref>::from_str(string.as_str()) + } + } + + impl_extra!(@all, $owned, $ref); + }; + (@all, $owned:path, $ref:path) => { + impl $ref { + /// Get a + #[doc = concat!("[`Cow<'_, ", stringify!($ref), ">`](std::borrow::Cow::Borrowed)")] + pub fn as_cow<'a>(&'a self) -> ::std::borrow::Cow<'a, $ref> { + self.into() + } + } + + impl<'a> From<&'a $owned> for &'a $ref { + fn from(owned: &'a $owned) -> Self { + &*owned + } + } + + impl<'a> From<&'a $owned> for ::std::borrow::Cow<'a, $ref> { + fn from(owned: &'a $owned) -> Self { + ::std::borrow::Cow::Borrowed(&*owned) + } + } + + impl<'a> crate::IntoCow<'a, $ref> for &'a $ref { + fn to_cow(self) -> ::std::borrow::Cow<'a, $ref> { + ::std::borrow::Cow::Borrowed(self) + } + } + + impl<'a> crate::IntoCow<'a, $ref> for $owned { + fn to_cow(self) -> ::std::borrow::Cow<'a, $ref> { + ::std::borrow::Cow::Owned(self) + } + } + + impl<'a> crate::IntoCow<'a, $ref> for &'a $owned { + fn to_cow(self) -> ::std::borrow::Cow<'a, $ref> { + ::std::borrow::Cow::Borrowed(self.as_ref()) + } + } + }; +} + +/// Convert a type into a [`Cow`](std::borrow::Cow) +pub trait IntoCow<'a, Ref: ?Sized> +where Ref: ToOwned { + /// Make the cow with proper ownership, muu + fn to_cow(self) -> std::borrow::Cow<'a, Ref> + where &'a Self: 'a; +} + +impl<'a, R> IntoCow<'a, R> for std::borrow::Cow<'a, R> +where + &'a R: Into<&'a R>, + R: ToOwned + ?Sized + 'a, + &'a R: Into>, + R::Owned: Into>, +{ + fn to_cow(self) -> std::borrow::Cow<'a, R> { + match self { + std::borrow::Cow::Borrowed(b) => b.into(), + std::borrow::Cow::Owned(o) => o.into(), + } + } +} + +impl<'a, R> IntoCow<'a, R> for &'a str +where + &'a str: Into<&'a R>, + R: ToOwned + ?Sized + 'a, +{ + fn to_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Borrowed(self.into()) } +} + +impl<'a, R> IntoCow<'a, R> for &'a String +where + &'a String: Into<&'a R>, + R: ToOwned + ?Sized + 'a, +{ + fn to_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Borrowed(self.into()) } +} + +impl<'a, R> IntoCow<'a, R> for String +where + String: Into, + R: ToOwned + ?Sized + 'a, +{ + fn to_cow(self) -> std::borrow::Cow<'a, R> { std::borrow::Cow::Owned(self.into()) } +} + mod basic; // cc: https://github.com/rust-lang/rust/issues/83428, can't use glob imports and keep the modules private #[cfg(feature = "color")] @@ -54,3 +162,35 @@ pub use crate::stream::*; pub use crate::time::*; #[cfg(feature = "user")] pub use crate::user::*; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lol() { + assert!(broadcaster_id("literal")); + assert!(!broadcaster_id(String::from("string"))); + assert!(broadcaster_id(&String::from("ref string"))); + assert!(broadcaster_id(UserIdRef::from_static("static ref"))); + assert!(!broadcaster_id(UserId::new(String::from("owned")))); + assert!(broadcaster_id(&UserId::new(String::from("borrowed owned")))); + assert!(broadcaster_id(&*UserId::new(String::from("deref owned")))); + assert!(!broadcaster_id(std::borrow::Cow::Owned(UserId::new( + String::from("cow owned") + )))); + assert!(broadcaster_id(std::borrow::Cow::Borrowed( + UserIdRef::from_static("cow borrowed") + ))); + } + /// aa + pub fn broadcaster_id<'a>(broadcaster_id: impl IntoCow<'a, UserIdRef> + 'a) -> bool { + struct K<'a> { + id: std::borrow::Cow<'a, UserIdRef>, + } + let k = K { + id: broadcaster_id.to_cow(), + }; + matches!(k.id, std::borrow::Cow::Borrowed(_)) + } +} diff --git a/src/moderation.rs b/src/moderation.rs index 117a170b..928a9f4f 100644 --- a/src/moderation.rs +++ b/src/moderation.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; #[aliri_braid::braid(serde)] pub struct BlockedTermId; +impl_extra!(BlockedTermId, BlockedTermIdRef); + /// Status of a message that is or was in AutoMod queue #[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)] #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] @@ -23,3 +25,5 @@ pub enum AutomodStatus { /// A message ID #[aliri_braid::braid(serde)] pub struct MsgId; + +impl_extra!(MsgId, MsgIdRef); diff --git a/src/points.rs b/src/points.rs index 3d4ee07d..a225c959 100644 --- a/src/points.rs +++ b/src/points.rs @@ -6,26 +6,38 @@ use crate::{DisplayName, UserId, UserName}; #[aliri_braid::braid(serde)] pub struct RewardId; +impl_extra!(RewardId, RewardIdRef); + /// A reward redemption ID. #[aliri_braid::braid(serde)] pub struct RedemptionId; +impl_extra!(RedemptionId, RedemptionIdRef); + /// A poll ID #[aliri_braid::braid(serde)] pub struct PollId; +impl_extra!(PollId, PollIdRef); + /// A poll choice ID #[aliri_braid::braid(serde)] pub struct PollChoiceId; +impl_extra!(PollChoiceId, PollChoiceIdRef); + /// A prediction ID #[aliri_braid::braid(serde)] pub struct PredictionId; +impl_extra!(PredictionId, PredictionIdRef); + /// A prediction choice ID #[aliri_braid::braid(serde)] pub struct PredictionOutcomeId; +impl_extra!(PredictionOutcomeId, PredictionOutcomeIdRef); + /// Reward redemption max #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] diff --git a/src/stream.rs b/src/stream.rs index ac75ed93..84d78044 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -4,38 +4,56 @@ use serde::{Deserialize, Serialize}; #[aliri_braid::braid(serde)] pub struct StreamId; +impl_extra!(StreamId, StreamIdRef); + /// A game or category ID #[aliri_braid::braid(serde)] pub struct CategoryId; +impl_extra!(CategoryId, CategoryIdRef); + /// A tag ID #[aliri_braid::braid(serde)] pub struct TagId; +impl_extra!(TagId, TagIdRef); + /// A Team ID #[aliri_braid::braid(serde)] pub struct TeamId; +impl_extra!(TeamId, TeamIdRef); + /// A video ID #[aliri_braid::braid(serde)] pub struct VideoId; +impl_extra!(VideoId, VideoIdRef); + /// A clip ID #[aliri_braid::braid(serde)] pub struct ClipId; +impl_extra!(ClipId, ClipIdRef); + /// A Stream Segment ID. #[aliri_braid::braid(serde)] pub struct StreamSegmentId; +impl_extra!(StreamSegmentId, StreamSegmentIdRef); + /// A Hype Train ID #[aliri_braid::braid(serde)] pub struct HypeTrainId; +impl_extra!(HypeTrainId, HypeTrainIdRef); + /// A Charity Campaign ID #[aliri_braid::braid(serde)] pub struct CharityCampaignId; +impl_extra!(CharityCampaignId, CharityCampaignIdRef); + /// A game or category as defined by Twitch #[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)] #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] diff --git a/src/time.rs b/src/time.rs index ef12a385..3cd701f9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -2,6 +2,8 @@ #[aliri_braid::braid(serde, validator, ord = "omit")] pub struct Timestamp; +impl_extra!(validated, Timestamp, TimestampRef, TimestampParseError); + impl aliri_braid::Validator for Timestamp { type Error = TimestampParseError;