Skip to content

Commit ea356be

Browse files
committed
eh
1 parent b7b3801 commit ea356be

File tree

10 files changed

+267
-124
lines changed

10 files changed

+267
-124
lines changed

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
32
"rust-analyzer.checkOnSave.command": "clippy",
43
"rust-analyzer.cargo.features": ["helix", "client"],
54
"rust-analyzer.checkOnSave.enable": true,

Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ aliri_braid = "0.3.1"
5858
futures = { version = "0.3.25", optional = true }
5959
hyper = { version = "0.14.23", optional = true }
6060
twitch_types = { version = "0.3.10", path = "./twitch_types" }
61+
yoke = { version = "0.6.2", features = ["derive"] }
6162

6263
[features]
6364
default = ["deser_borrow"]
@@ -69,83 +70,29 @@ deny_unknown_fields = []
6970
trace_unknown_fields = ["dep:serde_ignored", "tracing"]
7071

7172
serde_json = ["dep:serde_json", "dep:serde_path_to_error"]
72-
helix = [
73-
"twitch_types/color",
74-
"twitch_types/emote",
75-
"twitch_types/goal",
76-
"twitch_types/moderation",
77-
"twitch_types/points",
78-
"twitch_types/stream",
79-
"twitch_types/timestamp",
80-
"twitch_types/user",
81-
"dep:async-trait",
82-
"serde_json",
83-
"dep:hyper",
84-
]
73+
helix = ["twitch_types/color", "twitch_types/emote", "twitch_types/goal", "twitch_types/moderation", "twitch_types/points", "twitch_types/stream", "twitch_types/timestamp", "twitch_types/user", "dep:async-trait", "serde_json", "dep:hyper"]
8574

8675
deser_borrow = []
8776

8877
tmi = ["serde_json", "dep:serde_path_to_error", "dep:hyper"]
8978

90-
surf = [
91-
"dep:surf",
92-
"dep:http-types",
93-
"client",
94-
"twitch_oauth2/surf",
95-
"hyper?/stream",
96-
]
79+
surf = ["dep:surf", "dep:http-types", "client", "twitch_oauth2/surf", "hyper?/stream"]
9780

9881
ureq = ["dep:ureq", "client"]
9982

10083
reqwest = ["dep:reqwest", "client", "twitch_oauth2/reqwest"]
10184

102-
pubsub = [
103-
"serde_json",
104-
"twitch_types/emote",
105-
"twitch_types/moderation",
106-
"twitch_types/stream",
107-
"twitch_types/timestamp",
108-
"twitch_types/user",
109-
"twitch_types/points",
110-
]
85+
pubsub = ["serde_json", "twitch_types/emote", "twitch_types/moderation", "twitch_types/stream", "twitch_types/timestamp", "twitch_types/user", "twitch_types/points"]
11186

112-
eventsub = [
113-
"serde_json",
114-
"twitch_types/emote",
115-
"twitch_types/eventsub",
116-
"twitch_types/goal",
117-
"twitch_types/points",
118-
"twitch_types/stream",
119-
"twitch_types/timestamp",
120-
# insert when websockets are stable
121-
# "serde_json/raw_value",
122-
]
87+
eventsub = ["serde_json", "twitch_types/emote", "twitch_types/eventsub", "twitch_types/goal", "twitch_types/points", "twitch_types/stream", "twitch_types/timestamp"]
12388

12489
hmac = ["dep:crypto_hmac", "dep:sha2"]
12590

12691
mock_api = []
12792

128-
all = [
129-
"tmi",
130-
"helix",
131-
"client",
132-
"pubsub",
133-
"eventsub",
134-
"hmac",
135-
"twitch_oauth2",
136-
"tracing",
137-
"twitch_types/time",
138-
]
93+
all = ["tmi", "helix", "client", "pubsub", "eventsub", "hmac", "twitch_oauth2", "tracing", "twitch_types/time"]
13994

140-
_all = [
141-
"all",
142-
"typed-builder",
143-
"surf",
144-
"reqwest",
145-
"ureq",
146-
"twitch_oauth2/surf_client_curl",
147-
"mock_api",
148-
]
95+
_all = ["all", "typed-builder", "surf", "reqwest", "ureq", "twitch_oauth2/surf_client_curl", "mock_api"]
14996

15097
[dev-dependencies]
15198
tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros"] }
@@ -169,6 +116,12 @@ name = "get_channel_status"
169116
path = "examples/get_channel_status.rs"
170117
required-features = ["reqwest", "helix"]
171118

119+
[[example]]
120+
name = "get_users"
121+
path = "examples/get_users.rs"
122+
required-features = ["reqwest", "helix"]
123+
124+
172125
[[example]]
173126
name = "get_moderation"
174127
path = "examples/get_moderation.rs"

examples/get_users.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use twitch_api::HelixClient;
2+
use twitch_oauth2::{AccessToken, UserToken};
3+
4+
fn main() {
5+
use std::error::Error;
6+
if let Err(err) = run() {
7+
println!("Error: {err}");
8+
let mut e: &'_ dyn Error = err.as_ref();
9+
while let Some(cause) = e.source() {
10+
println!("Caused by: {cause}");
11+
e = cause;
12+
}
13+
}
14+
}
15+
16+
#[tokio::main]
17+
async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
18+
let _ = dotenvy::dotenv();
19+
let mut args = std::env::args().skip(1);
20+
let client: HelixClient<reqwest::Client> = HelixClient::default();
21+
let token = std::env::var("TWITCH_TOKEN")
22+
.ok()
23+
.or_else(|| args.next())
24+
.map(AccessToken::new)
25+
.expect("Please set env: TWITCH_TOKEN or pass token as first argument");
26+
let token = UserToken::from_existing(&client, token, None, None).await?;
27+
28+
let user = client
29+
.get_user_from_id(&*args.next().unwrap(), &token)
30+
.await?
31+
.expect("no user found");
32+
33+
println!("User information:\n\t{:#?}", user.get());
34+
Ok(())
35+
}

src/helix/client.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,13 @@ impl<'a, C: crate::HttpClient<'a>> HelixClient<'a, C> {
117117
&'a self,
118118
request: R,
119119
token: &T,
120-
) -> Result<Response<R, D>, ClientRequestError<<C as crate::HttpClient<'a>>::Error>>
120+
) -> Result<
121+
Response<R, yoke::Yoke<D, std::rc::Rc<[u8]>>>,
122+
ClientRequestError<<C as crate::HttpClient<'a>>::Error>,
123+
>
121124
where
122-
R: Request<Response = D> + Request + RequestGet,
123-
D: serde::de::DeserializeOwned + PartialEq,
125+
R: for <'y> Request<Response<'y> = D> + Request + RequestGet,
126+
D: for<'b> serde::de::Deserialize<'b> + for<'b> yoke::Yokeable<'b, Output = D> + PartialEq,
124127
T: TwitchToken + ?Sized,
125128
C: Send,
126129
{
@@ -133,7 +136,42 @@ impl<'a, C: crate::HttpClient<'a>> HelixClient<'a, C> {
133136
.map_err(ClientRequestError::RequestError)?
134137
.into_response_vec()
135138
.await?;
136-
<R>::parse_response(Some(request), &uri, response).map_err(Into::into)
139+
let (parts, body) = response.into_parts();
140+
let body: std::rc::Rc<[u8]> = body.into();
141+
let mut pagination = None;
142+
let mut request_opt = None;
143+
let mut total = None;
144+
let mut other = None;
145+
let resp: yoke::Yoke<<R as Request>::Response<'static>, _> =
146+
yoke::Yoke::try_attach_to_cart(
147+
body,
148+
|body| -> Result<
149+
<D as yoke::Yokeable<'_>>::Output,
150+
ClientRequestError<<C as crate::HttpClient<'a>>::Error>,
151+
> {
152+
let response = http::Response::from_parts(parts, body);
153+
let Response {
154+
data,
155+
pagination: pagination_inner,
156+
request: request_inner,
157+
total: total_inner,
158+
other: other_inner,
159+
} = <R>::parse_response(Some(request), &uri, &response)?;
160+
pagination = pagination_inner;
161+
request_opt = request_inner;
162+
total = total_inner;
163+
other = other_inner;
164+
Ok(data)
165+
},
166+
)?;
167+
168+
Ok(Response {
169+
data: resp,
170+
pagination,
171+
request: request_opt,
172+
total,
173+
other,
174+
})
137175
}
138176

139177
// /// Request on a valid [`RequestPost`] endpoint

src/helix/client/client_ext.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,20 @@ impl<'client, C: crate::HttpClient<'client> + Sync> HelixClient<'client, C> {
2828
// .map(|response| response.first())
2929
// }
3030

31-
// /// Get [User](helix::users::User) from user id
32-
// pub async fn get_user_from_id<T>(
33-
// &'client self,
34-
// id: impl Into<&types::UserIdRef>,
35-
// token: &T,
36-
// ) -> Result<Option<helix::users::User>, ClientError<'client, C>>
37-
// where
38-
// T: TwitchToken + ?Sized,
39-
// {
40-
// self.req_get(helix::users::GetUsersRequest::ids(&[id.into()][..]), token)
41-
// .await
42-
// .map(|response| response.first())
43-
// }
31+
/// Get [User](helix::users::User) from user id
32+
pub async fn get_user_from_id<T>(
33+
&'client self,
34+
id: impl Into<&types::UserIdRef>,
35+
token: &T,
36+
) -> Result<Option<yoke::Yoke<helix::users::User<'static>, std::rc::Rc<[u8]>>>, ClientError<'client, C>>
37+
where
38+
T: TwitchToken + ?Sized,
39+
{
40+
// self.req_get(helix::users::GetUsersRequest::ids(&[id.into()][..]), token)
41+
// .await
42+
// .map(|response| response.first_yoke())
43+
todo!()
44+
}
4445

4546
// /// Get multiple [User](helix::users::User)s from user ids.
4647
// pub async fn get_users_from_ids<T>(

src/helix/endpoints/users/get_users.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,30 +106,31 @@ impl<'a> GetUsersRequest<'a> {
106106
///
107107
/// [`get-users`](https://dev.twitch.tv/docs/api/reference#get-users)
108108
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
109+
#[derive(yoke::Yokeable)]
109110
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
110111
#[non_exhaustive]
111-
pub struct User {
112+
pub struct User<'a> {
112113
/// User’s broadcaster type: "partner", "affiliate", or "".
113114
pub broadcaster_type: Option<types::BroadcasterType>,
114115
/// Date when the user was created.
115-
pub created_at: types::Timestamp,
116+
pub created_at: Cow<'a, types::Timestamp>,
116117
/// User’s channel description.
117118
pub description: Option<String>,
118119
/// User’s display name.
119-
pub display_name: types::DisplayName,
120+
pub display_name: Cow<'a, types::DisplayName>,
120121
/// User’s email address. Returned if the request includes the [`user:read:email` scope](twitch_oauth2::Scope::UserReadEmail).
121122
pub email: Option<String>,
122123
/// User’s ID.
123-
pub id: types::UserId,
124+
pub id: Cow<'a, types::UserId>,
124125
/// User’s login name.
125-
pub login: types::UserName,
126+
pub login: Cow<'a, types::UserName>,
126127
/// URL of the user’s offline image.
127128
pub offline_image_url: Option<String>,
128129
/// URL of the user’s profile image.
129130
pub profile_image_url: Option<String>,
130131
/// User’s type: "staff", "admin", "global_mod", or "".
131132
#[serde(rename = "type")]
132-
pub type_: Option<types::UserType>,
133+
pub type_: Option<Cow<'a, types::UserType>>,
133134
#[deprecated(
134135
since = "0.7.0",
135136
note = "removed, see https://discuss.dev.twitch.tv/t/get-users-api-endpoint-view-count-deprecation/37777"
@@ -140,7 +141,7 @@ pub struct User {
140141
}
141142

142143
impl Request for GetUsersRequest<'_> {
143-
type Response = Vec<User>;
144+
type Response<'a> = Vec<User<'a>>;
144145

145146
#[cfg(feature = "twitch_oauth2")]
146147
const OPT_SCOPE: &'static [twitch_oauth2::Scope] = &[twitch_oauth2::Scope::UserReadEmail];

0 commit comments

Comments
 (0)