advent_of_code_data/client/
protocol.rs1use thiserror::Error;
2
3use crate::{Answer, Day, Part, Year};
4
5#[derive(Debug, Error)]
8pub enum ServiceError {
9 #[error("HTTP {}", .0)]
10 HttpStatusError(u16),
11 #[error("{}", .0)]
12 ReqwestError(#[from] reqwest::Error),
13}
14
15pub trait ServiceConnector {
18 fn get_input(&self, day: Day, year: Year, session: &str) -> Result<String, ServiceError>;
19 fn submit_answer(
20 &self,
21 answer: &Answer,
22 part: Part,
23 day: Day,
24 year: Year,
25 session: &str,
26 ) -> Result<String, ServiceError>;
27}
28
29#[derive(Debug)]
30pub struct AdventOfCodeService {}
31
32impl AdventOfCodeService {
33 const ADVENT_OF_CODE_DOMAIN: &'static str = "adventofcode.com";
34 const ADVENT_OF_CODE_URL: &'static str = "https://adventofcode.com";
35
36 fn create_http_client(
37 &self,
38 session: Option<&str>,
39 ) -> Result<reqwest::blocking::Client, ServiceError> {
40 let cookies: reqwest::cookie::Jar = Default::default();
43
44 if let Some(session) = session {
45 let cookie_data = format!(
46 "session={}; Domain={}",
47 session,
48 Self::ADVENT_OF_CODE_DOMAIN
49 );
50
51 tracing::debug!("adding session id `{}` to cookie jar", cookie_data);
52
53 cookies.add_cookie_str(
54 &cookie_data,
55 &Self::ADVENT_OF_CODE_URL.parse::<reqwest::Url>().unwrap(),
56 );
57 }
58
59 Ok(reqwest::blocking::ClientBuilder::new()
60 .cookie_provider(cookies.into())
61 .user_agent("github.com/smacdo/advent-of-code-rust [email: dev@smacdo.com]")
62 .build()?)
63 }
64}
65
66impl ServiceConnector for AdventOfCodeService {
67 fn get_input(&self, day: Day, year: Year, session: &str) -> Result<String, ServiceError> {
68 let url = format!("{}/{}/day/{}/input", Self::ADVENT_OF_CODE_URL, year, day);
69
70 tracing::debug!(
71 "url to get puzzle input for day {} year {} is `{}`",
72 day,
73 year,
74 url
75 );
76
77 let response = self.create_http_client(Some(session))?.get(url).send()?;
78 tracing::debug!("server responed with HTTP {}", response.status());
79
80 if response.status() == reqwest::StatusCode::OK {
81 Ok(response.text().unwrap()) } else {
83 Err(ServiceError::HttpStatusError(response.status().as_u16()))
84 }
85 }
86
87 fn submit_answer(
88 &self,
89 answer: &Answer,
90 part: Part,
91 day: Day,
92 year: Year,
93 session: &str,
94 ) -> Result<String, ServiceError> {
95 let url = format!("{}/{}/day/{}/answer", Self::ADVENT_OF_CODE_URL, year, day);
97
98 tracing::debug!(
99 "creating url to post puzzle answer for part {:?} day {} year {} answer `{}` with url = `{}`",
100 part,
101 day,
102 year,
103 answer,
104 url
105 );
106
107 let response = self
108 .create_http_client(Some(session))?
109 .post(url)
110 .form(&[
111 (
112 "level",
113 if part == Part::One {
114 "1".to_string()
115 } else {
116 "2".to_string()
117 },
118 ),
119 ("answer", answer.to_string()),
120 ])
121 .send()?;
122
123 tracing::debug!("server responed with HTTP {}", response.status());
124
125 if response.status() == reqwest::StatusCode::OK {
126 Ok(response.text().unwrap()) } else {
128 Err(ServiceError::HttpStatusError(response.status().as_u16()))
129 }
130 }
131}
132
133impl AdventOfCodeService {}