rusty_booru/shared/
client.rs1use std::{
2 fmt::{Debug, Display},
3 marker::PhantomData,
4};
5
6use crate::generic::{AutoCompleteItem, BooruPost, Rating};
7
8use super::{Sort, Tag, Tags};
9use itertools::Itertools;
10
11#[derive(Debug)]
12pub struct ClientBuilder<T: ClientTypes> {
13 pub client: reqwest::Client,
14 pub url: String,
15
16 _marker: PhantomData<T>,
17}
18
19impl<T: ClientTypes> Clone for ClientBuilder<T> {
20 fn clone(&self) -> Self {
21 Self {
22 client: self.client.clone(),
23 url: self.url.clone(),
24 _marker: self._marker,
25 }
26 }
27}
28
29pub trait ClientInformation {
30 const URL: &'static str;
31 const SORT: &'static str;
32}
33
34pub trait ClientTypes {
35 type Rating: From<Rating> + Display + Debug + Clone;
36 type Post: Into<BooruPost>;
37}
38
39pub type QueryVec = Vec<(String, String)>;
40
41pub enum QueryLike {
42 Gelbooru,
43}
44
45pub enum QueryMode<'a, T: ClientTypes + ClientInformation> {
46 Single(u32),
47 Multiple(&'a ClientQueryBuilder<T>),
48}
49
50pub trait WithClientBuilder<T: ClientTypes> {
51 fn builder() -> ClientBuilder<T>;
52}
53
54impl<T: ClientInformation + ClientTypes + From<ClientBuilder<T>>> WithClientBuilder<T> for T {
55 fn builder() -> ClientBuilder<T> {
56 ClientBuilder::new()
57 }
58}
59
60pub trait QueryDispatcher<T: ClientTypes> {
61 fn get_autocomplete<In: Into<String> + Send>(
62 &self,
63 input: In,
64 ) -> impl std::future::Future<Output = Result<Vec<AutoCompleteItem>, reqwest::Error>> + Send;
65
66 fn get_by_id(
68 &self,
69 id: u32,
70 ) -> impl std::future::Future<Output = Result<Option<T::Post>, crate::shared::Error>> + Send;
71
72 fn get(
74 &self,
75 ) -> impl std::future::Future<Output = Result<Vec<T::Post>, crate::shared::Error>> + Send;
76}
77
78pub trait WithCommonQuery {
79 fn common_query_type() -> QueryLike;
80}
81
82pub trait ImplementedWithCommonQuery<T: ClientTypes + ClientInformation> {
83 fn get_query(query_mode: QueryMode<T>) -> QueryVec;
84}
85
86impl<T: WithCommonQuery + ClientTypes + ClientInformation> ImplementedWithCommonQuery<T>
87 for ClientQueryDispatcher<T>
88{
89 fn get_query(query_mode: QueryMode<T>) -> QueryVec {
90 let query_type = T::common_query_type();
91
92 let mut base = match query_type {
93 QueryLike::Gelbooru => vec![
94 ("page", "dapi"),
95 ("s", "post"),
96 ("q", "index"),
97 ("json", "1"),
98 ],
99 }
100 .iter()
101 .map(|(k, v)| (k.to_string(), v.to_string()))
102 .collect_vec();
103
104 let extension = match query_type {
105 QueryLike::Gelbooru => match query_mode {
106 QueryMode::Single(id) => vec![("id", id.to_string())],
107 QueryMode::Multiple(query) => vec![
108 ("limit", query.limit.to_string()),
109 ("tags", query.tags.unpack()),
110 ],
111 },
112 }
113 .into_iter()
114 .map(|(k, v)| (k.to_string(), v))
115 .collect_vec();
116
117 base.extend(extension);
118 base
119 }
120}
121
122#[derive(Debug, Clone)]
129pub struct ClientQueryBuilder<T: ClientTypes> {
130 pub tags: Tags<T>,
131 pub limit: u32,
132}
133
134impl<T: ClientTypes + Clone> Default for ClientQueryBuilder<T> {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl<T: ClientTypes + Clone> ClientQueryBuilder<T> {
141 pub fn new() -> Self {
142 Self {
143 tags: Tags(Vec::new()),
144 limit: 100,
145 }
146 }
147
148 pub fn any_tag(&mut self, tag: Tag<T>) -> &mut Self {
149 self.tags.0.push(tag);
150 self
151 }
152
153 pub fn tag<S: ToString>(&mut self, tag: S) -> &mut Self {
154 self.any_tag(Tag::Plain(tag.to_string()))
155 }
156
157 pub fn sort(&mut self, sort: Sort) -> &mut Self {
158 self.any_tag(Tag::Sort(sort))
159 }
160
161 pub fn random(&mut self) -> &mut Self {
162 self.sort(Sort::Random)
163 }
164
165 pub fn rating(&mut self, rating: T::Rating) -> &mut Self {
166 self.any_tag(Tag::Rating(rating))
167 }
168
169 pub fn blacklist_tag<S: ToString>(&mut self, tag: S) -> &mut Self {
170 self.any_tag(Tag::Blacklist(tag.to_string()))
171 }
172
173 pub fn limit(&mut self, limit: u32) -> &mut Self {
175 self.limit = limit;
176 self
177 }
178}
179
180impl<T: ClientTypes + Clone> ClientBuilder<T> {
181 fn create_dispatcher(&self, query: &mut ClientQueryBuilder<T>) -> ClientQueryDispatcher<T> {
182 ClientQueryDispatcher {
183 builder: self.to_owned(),
184 query: query.clone(),
185 }
186 }
187
188 pub fn query_raw(&self, query: &mut ClientQueryBuilder<T>) -> ClientQueryDispatcher<T> {
189 self.create_dispatcher(query)
190 }
191
192 pub fn query(
193 &self,
194 query_fn: impl Fn(&mut ClientQueryBuilder<T>) -> &mut ClientQueryBuilder<T>,
195 ) -> ClientQueryDispatcher<T> {
196 self.query_raw(query_fn(&mut ClientQueryBuilder::new()))
197 }
198
199 pub fn dispatch(&self) -> ClientQueryDispatcher<T> {
201 self.create_dispatcher(&mut ClientQueryBuilder::new())
202 }
203}
204
205#[derive(Debug, Clone)]
206pub struct ClientQueryDispatcher<T: ClientTypes> {
207 pub builder: ClientBuilder<T>,
208 pub query: ClientQueryBuilder<T>,
209}
210
211impl<T: ClientInformation + ClientTypes> ClientBuilder<T> {
212 pub fn new() -> Self {
213 Self {
214 client: reqwest::Client::new(),
215 url: T::URL.to_string(),
216
217 _marker: PhantomData,
218 }
219 }
220
221 pub fn default_url(&mut self, url: &str) -> &mut Self {
223 self.url = url.to_string();
224 self
225 }
226}
227
228impl<T: ClientTypes + ClientInformation> Default for ClientBuilder<T> {
229 fn default() -> Self {
230 Self::new()
231 }
232}