twitch_api/helix/endpoints/eventsub/
create_eventsub_subscription.rs1use super::*;
5use crate::eventsub::{EventSubscription, EventType, Status, Transport, TransportResponse};
6
7#[derive(PartialEq, Eq, Serialize, Clone, Debug)]
11#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
12#[non_exhaustive]
13#[must_use]
14pub struct CreateEventSubSubscriptionRequest<E: EventSubscription> {
15 #[cfg_attr(feature = "typed-builder", builder(setter(skip), default))]
16 #[serde(skip)]
17 phantom: std::marker::PhantomData<E>,
18}
19
20impl<E: EventSubscription> CreateEventSubSubscriptionRequest<E> {
21 pub fn new() -> Self { Self::default() }
23}
24
25impl<E: EventSubscription> Default for CreateEventSubSubscriptionRequest<E> {
26 fn default() -> Self {
27 Self {
28 phantom: Default::default(),
29 }
30 }
31}
32
33impl<E: EventSubscription> helix::Request for CreateEventSubSubscriptionRequest<E> {
34 type Response = CreateEventSubSubscription<E>;
35
36 #[cfg(feature = "twitch_oauth2")]
37 const OPT_SCOPE: &'static [twitch_oauth2::Scope] = E::OPT_SCOPE;
38 const PATH: &'static str = "eventsub/subscriptions";
39 #[cfg(feature = "twitch_oauth2")]
40 const SCOPE: twitch_oauth2::Validator = E::SCOPE;
41}
42
43#[derive(PartialEq, Eq, Deserialize, Clone, Debug)]
51#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
52#[non_exhaustive]
53pub struct CreateEventSubSubscriptionBody<E: EventSubscription> {
54 #[serde(bound(deserialize = "E: EventSubscription"))]
56 pub subscription: E,
57 pub transport: Transport,
59}
60
61impl<E: EventSubscription> helix::HelixRequestBody for CreateEventSubSubscriptionBody<E> {
62 fn try_to_body(&self) -> Result<hyper::body::Bytes, helix::BodyError> {
63 #[derive(PartialEq, Serialize, Debug)]
64 struct IEventSubRequestBody<'a> {
65 r#type: EventType,
66 version: &'static str,
67 condition: serde_json::Value,
68 transport: &'a Transport,
69 }
70
71 let b = IEventSubRequestBody {
72 r#type: E::EVENT_TYPE,
73 version: E::VERSION,
74 condition: self.subscription.condition()?,
75 transport: &self.transport,
76 };
77 serde_json::to_vec(&b).map_err(Into::into).map(Into::into)
78 }
79}
80
81impl<E: EventSubscription> CreateEventSubSubscriptionBody<E> {
83 pub const fn new(subscription: E, transport: Transport) -> Self {
85 Self {
86 subscription,
87 transport,
88 }
89 }
90}
91
92#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
96#[non_exhaustive]
97pub struct CreateEventSubSubscription<E: EventSubscription> {
98 pub id: types::EventSubId,
100 pub status: Status,
102 #[serde(rename = "type")]
104 pub type_: EventType,
105 pub version: String,
107 #[serde(bound(deserialize = "E: EventSubscription"))]
109 pub condition: E,
110 pub created_at: types::Timestamp,
112 pub transport: TransportResponse,
114 pub total: usize,
116 pub total_cost: usize,
118 pub max_total_cost: usize,
120 pub cost: usize,
122}
123
124impl<E: EventSubscription> helix::RequestPost for CreateEventSubSubscriptionRequest<E> {
125 type Body = CreateEventSubSubscriptionBody<E>;
126
127 fn parse_inner_response(
128 request: Option<Self>,
129 uri: &http::Uri,
130 text: &str,
131 status: http::StatusCode,
132 ) -> Result<helix::Response<Self, Self::Response>, helix::HelixRequestPostError>
133 where
134 Self: Sized,
135 {
136 #[derive(PartialEq, Eq, Deserialize, Debug)]
137 #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
138 pub struct InnerResponseData<E: EventSubscription> {
139 cost: usize,
140 #[serde(bound(deserialize = "E: EventSubscription"))]
141 condition: E,
142 created_at: types::Timestamp,
143 id: types::EventSubId,
144 status: Status,
145 transport: TransportResponse,
146 #[serde(rename = "type")]
147 type_: EventType,
148 version: String,
149 }
150 #[derive(PartialEq, Deserialize, Debug)]
151 #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
152 struct InnerResponse<E: EventSubscription> {
153 #[serde(bound(deserialize = "E: EventSubscription"))]
154 data: Vec<InnerResponseData<E>>,
155 limit: Option<usize>,
156 total: usize,
157 total_cost: usize,
158 max_total_cost: usize,
159 }
160 let response: InnerResponse<E> = helix::parse_json(text, true).map_err(|e| {
161 helix::HelixRequestPostError::DeserializeError(text.to_string(), e, uri.clone(), status)
162 })?;
163 let data = response.data.into_iter().next().ok_or_else(|| {
164 helix::HelixRequestPostError::InvalidResponse {
165 reason: "missing response data",
166 response: text.to_string(),
167 status,
168 uri: uri.clone(),
169 }
170 })?;
171 Ok(helix::Response::with_data(
172 CreateEventSubSubscription {
173 total: response.total,
175 total_cost: response.total_cost,
176 max_total_cost: response.max_total_cost,
177 cost: data.cost,
178 id: data.id,
179 status: data.status,
180 type_: data.type_,
181 version: data.version,
182 condition: data.condition,
183 created_at: data.created_at,
184 transport: data.transport,
185 },
186 request,
187 ))
188 }
189}
190
191#[cfg(test)]
192#[test]
193fn test_request() {
194 use crate::eventsub::{self, user::UserUpdateV1};
195 use helix::*;
196 let req: CreateEventSubSubscriptionRequest<UserUpdateV1> =
197 CreateEventSubSubscriptionRequest::default();
198
199 let sub = UserUpdateV1::new("1234");
200 let transport =
201 eventsub::Transport::webhook("https://this-is-a-callback.com", "s3cre7".to_string());
202
203 let body = CreateEventSubSubscriptionBody::new(sub, transport);
204
205 assert_eq!(
206 std::str::from_utf8(&body.try_to_body().unwrap()).unwrap(),
207 r#"{"type":"user.update","version":"1","condition":{"user_id":"1234"},"transport":{"method":"webhook","callback":"https://this-is-a-callback.com","secret":"s3cre7"}}"#
208 );
209
210 dbg!(req.create_request(body, "token", "clientid").unwrap());
211
212 let data = br#"{
215 "data": [
216 {
217 "id": "26b1c993-bfcf-44d9-b876-379dacafe75a",
218 "status": "webhook_callback_verification_pending",
219 "type": "user.update",
220 "version": "1",
221 "condition": {
222 "user_id": "1234"
223 },
224 "created_at": "2020-11-10T14:32:18.730260295Z",
225 "transport": {
226 "method": "webhook",
227 "callback": "https://this-is-a-callback.com"
228 },
229 "cost": 1
230 }
231 ],
232 "total": 1,
233 "total_cost": 1,
234 "max_total_cost": 10000
235}
236 "#
237 .to_vec();
238 let http_response = http::Response::builder().status(202).body(data).unwrap();
239
240 let uri = req.get_uri().unwrap();
241 assert_eq!(
242 uri.to_string(),
243 "https://api.twitch.tv/helix/eventsub/subscriptions?"
244 );
245
246 dbg!(
247 "{:#?}",
248 CreateEventSubSubscriptionRequest::parse_response(Some(req), &uri, http_response).unwrap()
249 );
250}