twitch_api/helix/endpoints/predictions/
create_prediction.rs

1//! Create a Channel Points Prediction for a specific Twitch channel.
2//!
3//! Results are ordered by most recent, so it can be assumed that the currently active or locked Prediction will be the first item.
4//! [`create-prediction`](https://dev.twitch.tv/docs/api/reference#create-prediction)
5//!
6//! # Accessing the endpoint
7//!
8//! ## Request: [CreatePredictionRequest]
9//!
10//! To use this endpoint, construct a [`CreatePredictionRequest`] with the [`CreatePredictionRequest::new()`] method.
11//!
12//! ```rust
13//! use twitch_api::helix::predictions::create_prediction;
14//! let request = create_prediction::CreatePredictionRequest::new();
15//! ```
16//!
17//! ## Body: [CreatePredictionBody]
18//!
19//! We also need to provide a body to the request containing what we want to change.
20//!
21//! ```
22//! # use twitch_api::helix::predictions::create_prediction;
23//! let outcomes = &[
24//!     create_prediction::NewPredictionOutcome::new("Yes, give it time."),
25//!     create_prediction::NewPredictionOutcome::new("Definitely not."),
26//! ];
27//! let body = create_prediction::CreatePredictionBody::new(
28//!     "141981764",
29//!     "Any leeks in the stream?",
30//!     outcomes,
31//!     120,
32//! );
33//! ```
34//!
35//! ## Response: [CreatePredictionResponse]
36//!
37//!
38//! Send the request to receive the response with [`HelixClient::req_post()`](helix::HelixClient::req_post).
39//!
40//!
41//! ```rust, no_run
42//! use twitch_api::helix::{self, predictions::create_prediction};
43//! # use twitch_api::client;
44//! # #[tokio::main]
45//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
46//! # let client: helix::HelixClient<'static, client::DummyHttpClient> = helix::HelixClient::default();
47//! # let token = twitch_oauth2::AccessToken::new("validtoken".to_string());
48//! # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?;
49//! let request = create_prediction::CreatePredictionRequest::new();
50//! let outcomes = &[
51//!     create_prediction::NewPredictionOutcome::new("Yes, give it time."),
52//!     create_prediction::NewPredictionOutcome::new("Definitely not."),
53//! ];
54//! let body = create_prediction::CreatePredictionBody::new(
55//!     "141981764",
56//!     "Any leeks in the stream?",
57//!     outcomes,
58//!     120,
59//! );
60//! let response: create_prediction::CreatePredictionResponse = client.req_post(request, body, &token).await?.data;
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! You can also get the [`http::Request`] with [`request.create_request(&token, &client_id)`](helix::RequestPost::create_request)
66//! and parse the [`http::Response`] with [`CreatePredictionRequest::parse_response(None, &request.get_uri(), response)`](CreatePredictionRequest::parse_response)
67
68use std::marker::PhantomData;
69
70use super::*;
71use helix::RequestPost;
72
73/// Query Parameters for [Create Prediction](super::create_prediction)
74///
75/// [`create-prediction`](https://dev.twitch.tv/docs/api/reference#create-prediction)
76#[derive(PartialEq, Eq, Deserialize, Serialize, Clone, Debug, Default)]
77#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
78#[must_use]
79#[non_exhaustive]
80pub struct CreatePredictionRequest<'a> {
81    #[cfg_attr(feature = "typed-builder", builder(default))]
82    #[serde(skip)]
83    _marker: PhantomData<&'a ()>,
84}
85
86impl CreatePredictionRequest<'_> {
87    /// Create a new [`CreatePredictionRequest`]
88    pub fn new() -> Self { Self::default() }
89}
90
91/// Body Parameters for [Create Prediction](super::create_prediction)
92///
93/// [`create-prediction`](https://dev.twitch.tv/docs/api/reference#create-prediction)
94#[derive(PartialEq, Eq, Deserialize, Serialize, Clone, Debug)]
95#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
96#[non_exhaustive]
97pub struct CreatePredictionBody<'a> {
98    /// The broadcaster running Predictions. Provided broadcaster_id must match the user_id in the user OAuth token.
99    #[cfg_attr(feature = "typed-builder", builder(setter(into)))]
100    #[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
101    pub broadcaster_id: Cow<'a, types::UserIdRef>,
102    /// Title for the Prediction. Maximum: 45 characters.
103    #[cfg_attr(feature = "typed-builder", builder(setter(into)))]
104    #[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
105    pub title: Cow<'a, str>,
106    /// Array of outcome objects with titles for the Prediction. Minimum: 2. Maximum: 10.
107    #[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
108    pub outcomes: Cow<'a, [NewPredictionOutcome<'a>]>,
109    /// Total duration for the Prediction (in seconds). Minimum: 1. Maximum: 1800.
110    pub prediction_window: i64,
111}
112
113impl<'a> CreatePredictionBody<'a> {
114    /// Create a Channel Points Prediction for a specific Twitch channel.
115    pub fn new(
116        broadcaster_id: impl types::IntoCow<'a, types::UserIdRef> + 'a,
117        title: impl Into<Cow<'a, str>>,
118        outcomes: &'a [NewPredictionOutcome<'a>],
119        prediction_window: i64,
120    ) -> Self {
121        Self {
122            broadcaster_id: broadcaster_id.into_cow(),
123            title: title.into(),
124            outcomes: outcomes.into(),
125            prediction_window,
126        }
127    }
128}
129
130impl helix::private::SealedSerialize for CreatePredictionBody<'_> {}
131
132/// Choice settings for a poll
133#[derive(PartialEq, Eq, Deserialize, Serialize, Clone, Debug)]
134#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
135#[non_exhaustive]
136pub struct NewPredictionOutcome<'a> {
137    /// Text displayed for the choice. Maximum: 25 characters.
138    #[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
139    pub title: Cow<'a, str>,
140}
141
142impl<'a> NewPredictionOutcome<'a> {
143    /// Create a new [`NewPredictionOutcome`]
144    pub fn new(title: impl Into<Cow<'a, str>>) -> Self {
145        Self {
146            title: title.into(),
147        }
148    }
149}
150
151/// Return Values for [Create Prediction](super::create_prediction)
152///
153/// [`create-prediction`](https://dev.twitch.tv/docs/api/reference#create-prediction)
154pub type CreatePredictionResponse = super::Prediction;
155
156impl Request for CreatePredictionRequest<'_> {
157    type Response = CreatePredictionResponse;
158
159    const PATH: &'static str = "predictions";
160    #[cfg(feature = "twitch_oauth2")]
161    const SCOPE: twitch_oauth2::Validator =
162        twitch_oauth2::validator![twitch_oauth2::Scope::ChannelManagePredictions];
163}
164
165impl<'a> RequestPost for CreatePredictionRequest<'a> {
166    type Body = CreatePredictionBody<'a>;
167
168    fn parse_inner_response(
169        request: Option<Self>,
170        uri: &http::Uri,
171        response_str: &str,
172        status: http::StatusCode,
173    ) -> Result<helix::Response<Self, Self::Response>, helix::HelixRequestPostError>
174    where
175        Self: Sized,
176    {
177        let response: helix::InnerResponse<Vec<Self::Response>> =
178            helix::parse_json(response_str, true).map_err(|e| {
179                helix::HelixRequestPostError::DeserializeError(
180                    response_str.to_string(),
181                    e,
182                    uri.clone(),
183                    status,
184                )
185            })?;
186        let data = response.data.into_iter().next().ok_or_else(|| {
187            helix::HelixRequestPostError::InvalidResponse {
188                reason: "response included no data",
189                response: response_str.to_string(),
190                status,
191                uri: uri.clone(),
192            }
193        })?;
194        Ok(helix::Response {
195            data,
196            pagination: response.pagination.cursor,
197            request,
198            total: None,
199            other: None,
200        })
201    }
202}
203
204#[cfg(test)]
205#[test]
206fn test_request() {
207    use helix::*;
208    let req = CreatePredictionRequest::new();
209    let outcomes = &[
210        NewPredictionOutcome::new("Yes, give it time."),
211        NewPredictionOutcome::new("Definitely not."),
212    ];
213    let body = CreatePredictionBody::new("141981764", "Any leeks in the stream?", outcomes, 120);
214
215    assert_eq!(
216        std::str::from_utf8(&body.try_to_body().unwrap()).unwrap(),
217        r#"{"broadcaster_id":"141981764","title":"Any leeks in the stream?","outcomes":[{"title":"Yes, give it time."},{"title":"Definitely not."}],"prediction_window":120}"#
218    );
219
220    dbg!(req.create_request(body, "token", "clientid").unwrap());
221
222    // From twitch docs
223    let data = br##"
224{
225    "data": [
226        {
227        "id": "bc637af0-7766-4525-9308-4112f4cbf178",
228        "broadcaster_id": "141981764",
229        "broadcaster_name": "TwitchDev",
230        "broadcaster_login": "twitchdev",
231        "title": "Any leeks in the stream?",
232        "winning_outcome_id": null,
233        "outcomes": [
234            {
235            "id": "73085848-a94d-4040-9d21-2cb7a89374b7",
236            "title": "Yes, give it time.",
237            "users": 0,
238            "channel_points": 0,
239            "top_predictors": null,
240            "color": "BLUE"
241            },
242            {
243            "id": "906b70ba-1f12-47ea-9e95-e5f93d20e9cc",
244            "title": "Definitely not.",
245            "users": 0,
246            "channel_points": 0,
247            "top_predictors": null,
248            "color": "PINK"
249            }
250        ],
251        "prediction_window": 120,
252        "status": "ACTIVE",
253        "created_at": "2021-04-28T17:11:22.595914172Z",
254        "ended_at": null,
255        "locked_at": null
256        }
257    ]
258}
259    "##
260    .to_vec();
261
262    let http_response = http::Response::builder().status(200).body(data).unwrap();
263    // This is marked as 204 in twitch docs, but in reality it's 200
264
265    let uri = req.get_uri().unwrap();
266    assert_eq!(uri.to_string(), "https://api.twitch.tv/helix/predictions?");
267
268    dbg!(CreatePredictionRequest::parse_response(Some(req), &uri, http_response).unwrap());
269}