|
1 | 1 | use reqwest::{Client, Error, Method, Response};
|
2 | 2 |
|
| 3 | +macro_rules! filter { |
| 4 | + ( $( $op:ident ),* ) => { |
| 5 | + $( |
| 6 | + pub fn $op(mut self, column: &str, param: &str) -> Self { |
| 7 | + self.queries.push((column.to_string(), |
| 8 | + format!("{}.{}", stringify!($op), param))); |
| 9 | + self |
| 10 | + } |
| 11 | + )* |
| 12 | + } |
| 13 | +} |
| 14 | + |
3 | 15 | pub struct Builder {
|
4 |
| - method: Option<Method>, |
| 16 | + method: Method, |
5 | 17 | url: String,
|
6 | 18 | queries: Vec<(String, String)>,
|
7 | 19 | headers: Vec<(String, String)>,
|
8 | 20 | body: Option<String>,
|
9 | 21 | }
|
10 | 22 |
|
| 23 | +// TODO: Complex filters (not, and, or) |
| 24 | +// TODO: Switching schema |
| 25 | +// TODO: Exact, planned, estimated count (HEAD verb) |
| 26 | +// TODO: Response format |
| 27 | +// TODO: Embedded resources |
11 | 28 | impl Builder {
|
12 |
| - // TODO: Schema |
13 | 29 | pub fn new(url: &str) -> Self {
|
14 | 30 | Builder {
|
15 |
| - method: None, |
| 31 | + method: Method::GET, |
16 | 32 | url: url.to_string(),
|
17 | 33 | queries: Vec::new(),
|
18 | 34 | headers: Vec::new(),
|
19 | 35 | body: None,
|
20 | 36 | }
|
21 | 37 | }
|
22 | 38 |
|
| 39 | + // TODO: Multiple columns |
| 40 | + // TODO: Renaming columns |
| 41 | + // TODO: Casting columns |
| 42 | + // TODO: JSON columns |
| 43 | + // TODO: Computed (virtual) columns |
| 44 | + // TODO: Investigate character corner cases (Unicode, [ .,:()]) |
23 | 45 | pub fn select(mut self, column: &str) -> Self {
|
24 |
| - self.method = Some(Method::GET); |
25 |
| - let column = column.chars().filter(|c| !c.is_whitespace()).collect(); |
26 |
| - self.queries.push(("select".to_string(), column)); |
| 46 | + self.method = Method::GET; |
| 47 | + self.queries |
| 48 | + .push(("select".to_string(), column.to_string())); |
| 49 | + self |
| 50 | + } |
| 51 | + |
| 52 | + // TODO: desc/asc |
| 53 | + // TODO: nullsfirst/nullslast |
| 54 | + // TODO: Multiple columns |
| 55 | + // TODO: Computed columns |
| 56 | + pub fn order(mut self, column: &str) -> Self { |
| 57 | + self.queries.push(("order".to_string(), column.to_string())); |
| 58 | + self |
| 59 | + } |
| 60 | + |
| 61 | + // TODO: Open-ended range |
| 62 | + pub fn limit(mut self, count: usize) -> Self { |
| 63 | + self.headers |
| 64 | + .push(("Content-Range".to_string(), format!("0-{}", count - 1))); |
| 65 | + self |
| 66 | + } |
| 67 | + |
| 68 | + pub fn single(mut self) -> Self { |
| 69 | + self.headers.push(( |
| 70 | + "Accept".to_string(), |
| 71 | + "application/vnd.pgrst.object+json".to_string(), |
| 72 | + )); |
27 | 73 | self
|
28 | 74 | }
|
29 | 75 |
|
30 | 76 | // TODO: Write-only tables
|
31 |
| - // TODO: UPSERT |
32 | 77 | // TODO: URL-encoded payload
|
| 78 | + // TODO: Allow specifying columns |
33 | 79 | pub fn insert(mut self, body: &str) -> Self {
|
34 |
| - self.method = Some(Method::POST); |
| 80 | + self.method = Method::POST; |
| 81 | + self.headers |
| 82 | + .push(("Prefer".to_string(), "return=representation".to_string())); |
| 83 | + self.body = Some(body.to_string()); |
| 84 | + self |
| 85 | + } |
| 86 | + |
| 87 | + pub fn insert_csv(mut self, body: &str) -> Self { |
| 88 | + self.headers |
| 89 | + .push(("Content-Type".to_string(), "text/csv".to_string())); |
| 90 | + self.insert(body) |
| 91 | + } |
| 92 | + |
| 93 | + // TODO: Allow Prefer: resolution=ignore-duplicates |
| 94 | + // TODO: on_conflict (make UPSERT work on UNIQUE columns) |
| 95 | + pub fn upsert(mut self, body: &str) -> Self { |
| 96 | + self.method = Method::POST; |
| 97 | + self.headers.push(( |
| 98 | + "Prefer".to_string(), |
| 99 | + "return=representation; resolution=merge-duplicates".to_string(), |
| 100 | + )); |
| 101 | + self.body = Some(body.to_string()); |
| 102 | + self |
| 103 | + } |
| 104 | + |
| 105 | + pub fn single_upsert(mut self, primary_column: &str, key: &str, body: &str) -> Self { |
| 106 | + self.method = Method::PUT; |
35 | 107 | self.headers
|
36 | 108 | .push(("Prefer".to_string(), "return=representation".to_string()));
|
| 109 | + self.queries |
| 110 | + .push((primary_column.to_string(), format!("eq.{}", key))); |
37 | 111 | self.body = Some(body.to_string());
|
38 | 112 | self
|
39 | 113 | }
|
40 | 114 |
|
41 | 115 | pub fn update(mut self, body: &str) -> Self {
|
42 |
| - self.method = Some(Method::PATCH); |
| 116 | + self.method = Method::PATCH; |
43 | 117 | self.headers
|
44 | 118 | .push(("Prefer".to_string(), "return=representation".to_string()));
|
45 | 119 | self.body = Some(body.to_string());
|
46 | 120 | self
|
47 | 121 | }
|
48 | 122 |
|
49 | 123 | pub fn delete(mut self) -> Self {
|
50 |
| - self.method = Some(Method::DELETE); |
| 124 | + self.method = Method::DELETE; |
51 | 125 | self.headers
|
52 | 126 | .push(("Prefer".to_string(), "return=representation".to_string()));
|
53 | 127 | self
|
54 | 128 | }
|
55 | 129 |
|
| 130 | + pub fn in_set(mut self, column: &str, param: &str) -> Self { |
| 131 | + self.queries |
| 132 | + .push((column.to_string(), format!("in.{}", param))); |
| 133 | + self |
| 134 | + } |
| 135 | + |
| 136 | + // It's unfortunate that `in` is a keyword, otherwise it'd belong in the |
| 137 | + // collection of filters below |
| 138 | + filter!( |
| 139 | + eq, gt, gte, lt, lte, neq, like, ilike, is, fts, plfts, phfts, wfts, cs, cd, ov, sl, sr, |
| 140 | + nxr, nxl, adj, not |
| 141 | + ); |
| 142 | + |
56 | 143 | pub async fn execute(self) -> Result<Response, Error> {
|
57 |
| - let mut req = Client::new().request(self.method.unwrap(), &self.url); |
| 144 | + let mut req = Client::new().request(self.method, &self.url); |
58 | 145 | for (k, v) in &self.headers {
|
59 | 146 | req = req.header(k, v);
|
60 | 147 | }
|
|
0 commit comments