spo_postgrest/
builder.rs

1use reqwest::{
2    header::{HeaderMap, HeaderValue},
3    Client, Error, Method, Response,
4};
5use serde::{Deserialize, Serialize};
6
7pub mod headers_serde {
8    use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
9    use serde::{
10        de::{Deserialize, Error},
11        ser::SerializeSeq,
12    };
13
14    pub fn serialize<S: serde::Serializer>(map: &HeaderMap, s: S) -> Result<S::Ok, S::Error> {
15        struct Bytes<'a>(&'a [u8]);
16
17        impl<'a> serde::Serialize for Bytes<'a> {
18            fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
19            where
20                S: serde::Serializer,
21            {
22                s.serialize_bytes(self.0)
23            }
24        }
25
26        let mut seq = s.serialize_seq(Some(map.len()))?;
27        for (k, v) in map.iter() {
28            match v.to_str() {
29                Ok(s) => seq.serialize_element(&(k.as_str(), s))?,
30                Err(_) => seq.serialize_element(&(k.as_str(), &Bytes(v.as_bytes())))?,
31            }
32        }
33        seq.end()
34    }
35
36    pub struct Name(HeaderName);
37
38    pub struct NameVisitor;
39
40    impl<'de> serde::de::Visitor<'de> for NameVisitor {
41        type Value = Name;
42
43        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
44            formatter.write_str("string")
45        }
46
47        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
48        where
49            E: Error,
50        {
51            Ok(Name(HeaderName::from_bytes(v).map_err(E::custom)?))
52        }
53
54        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
55        where
56            E: Error,
57        {
58            self.visit_bytes(v.as_bytes())
59        }
60    }
61
62    impl<'de> Deserialize<'de> for Name {
63        fn deserialize<D>(d: D) -> Result<Self, D::Error>
64        where
65            D: serde::Deserializer<'de>,
66        {
67            d.deserialize_str(NameVisitor)
68        }
69    }
70
71    pub struct Value(HeaderValue);
72
73    pub struct ValueVisitor;
74
75    impl<'de> serde::de::Visitor<'de> for ValueVisitor {
76        type Value = Value;
77
78        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
79            formatter.write_str("string or bytes")
80        }
81
82        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
83        where
84            E: Error,
85        {
86            Ok(Value(HeaderValue::from_bytes(v).map_err(E::custom)?))
87        }
88
89        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
90        where
91            E: Error,
92        {
93            Ok(Value(HeaderValue::from_str(v).map_err(E::custom)?))
94        }
95    }
96
97    impl<'de> Deserialize<'de> for Value {
98        fn deserialize<D>(d: D) -> Result<Self, D::Error>
99        where
100            D: serde::Deserializer<'de>,
101        {
102            d.deserialize_any(ValueVisitor)
103        }
104    }
105
106    pub struct Visitor;
107
108    impl<'de> serde::de::Visitor<'de> for Visitor {
109        type Value = HeaderMap;
110
111        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
112            formatter.write_str("[(string, string|bytes)]")
113        }
114
115        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
116        where
117            A: serde::de::SeqAccess<'de>,
118        {
119            let mut map = HeaderMap::with_capacity(seq.size_hint().unwrap_or(0));
120            while let Some((name, value)) = seq.next_element::<(Name, Value)>()? {
121                map.insert(name.0, value.0);
122            }
123            Ok(map)
124        }
125    }
126
127    pub fn deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<HeaderMap, D::Error> {
128        d.deserialize_seq(Visitor)
129    }
130}
131
132pub mod method_serde {
133    use reqwest::Method;
134    use serde::de::{Deserialize, Error};
135
136    pub fn serialize<S: serde::Serializer>(method: &Method, s: S) -> Result<S::Ok, S::Error> {
137        s.serialize_str(method.as_str())
138    }
139
140    pub fn deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Method, D::Error> {
141        String::deserialize(d)?.parse().map_err(D::Error::custom)
142    }
143}
144
145#[derive(Serialize, Deserialize, Debug, Clone)]
146pub struct Query {
147    #[serde(with = "method_serde")]
148    pub method: Method,
149    pub url: String,
150    pub schema: Option<String>,
151    // Need this to allow access from `filter.rs`
152    pub queries: Vec<(String, String)>,
153    #[serde(with = "headers_serde")]
154    pub headers: HeaderMap,
155    pub body: Option<String>,
156    pub is_rpc: bool,
157}
158
159impl From<Builder> for Query {
160    fn from(
161        Builder {
162            method,
163            url,
164            schema,
165            queries,
166            headers,
167            body,
168            is_rpc,
169            ..
170        }: Builder,
171    ) -> Self {
172        Self {
173            method,
174            url,
175            schema,
176            queries,
177            headers,
178            body,
179            is_rpc,
180        }
181    }
182}
183
184/// QueryBuilder struct
185#[derive(Clone, Debug)]
186pub struct Builder {
187    method: Method,
188    url: String,
189    schema: Option<String>,
190    // Need this to allow access from `filter.rs`
191    pub(crate) queries: Vec<(String, String)>,
192    headers: HeaderMap,
193    body: Option<String>,
194    is_rpc: bool,
195    // sharing a client is a good idea, performance wise
196    // the client has to live at least as much as the builder
197    pub client: Client,
198}
199
200// TODO: Test Unicode support
201impl Builder {
202    pub fn from_query(
203        Query {
204            method,
205            url,
206            schema,
207            queries,
208            headers,
209            body,
210            is_rpc,
211        }: Query,
212        client: Client,
213    ) -> Self {
214        Self {
215            method,
216            url,
217            schema,
218            queries,
219            headers,
220            body,
221            is_rpc,
222            client,
223        }
224    }
225
226    /// Creates a new `Builder` with the specified `schema`.
227    pub fn new<T>(url: T, schema: Option<String>, headers: HeaderMap, client: Client) -> Self
228    where
229        T: Into<String>,
230    {
231        let url = url.into().trim_end_matches('/').to_string();
232
233        let mut builder = Builder {
234            method: Method::GET,
235            url,
236            schema,
237            queries: Vec::new(),
238            headers,
239            body: None,
240            is_rpc: false,
241            client,
242        };
243        builder
244            .headers
245            .insert("Accept", HeaderValue::from_static("application/json"));
246        builder
247    }
248
249    /// Authenticates the request with JWT.
250    ///
251    /// # Example
252    ///
253    /// ```
254    /// use postgrest::Postgrest;
255    ///
256    /// let client = Postgrest::new("https://your.postgrest.endpoint");
257    /// client
258    ///     .from("table")
259    ///     .auth("supers.ecretjw.ttoken");
260    /// ```
261    pub fn auth<T>(mut self, token: T) -> Self
262    where
263        T: AsRef<str>,
264    {
265        self.headers.insert(
266            "Authorization",
267            HeaderValue::from_str(&format!("Bearer {}", token.as_ref())).unwrap(),
268        );
269        self
270    }
271
272    /// Performs horizontal filtering with SELECT.
273    ///
274    /// # Note
275    ///
276    /// `columns` is whitespace-sensitive, so you need to omit them unless your
277    /// column name contains whitespaces.
278    ///
279    /// # Example
280    ///
281    /// Simple example:
282    ///
283    /// ```
284    /// use postgrest::Postgrest;
285    ///
286    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
287    /// let client = Postgrest::new("https://your.postgrest.endpoint");
288    /// let resp = client
289    ///     .from("table")
290    ///     .select("*")
291    ///     .execute()
292    ///     .await?;
293    /// # Ok(())
294    /// # }
295    /// ```
296    ///
297    /// Renaming columns:
298    ///
299    /// ```
300    /// # use postgrest::Postgrest;
301    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
302    /// # let client = Postgrest::new("https://your.postgrest.endpoint");
303    /// let resp = client
304    ///     .from("users")
305    ///     .select("name:very_very_long_column_name")
306    ///     .execute()
307    ///     .await?;
308    /// # Ok(())
309    /// # }
310    /// ```
311    ///
312    /// Casting columns:
313    ///
314    /// ```
315    /// # use postgrest::Postgrest;
316    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
317    /// # let client = Postgrest::new("https://your.postgrest.endpoint");
318    /// let resp = client
319    ///     .from("users")
320    ///     .select("age::text")
321    ///     .execute()
322    ///     .await?;
323    /// # Ok(())
324    /// # }
325    /// ```
326    ///
327    /// SELECTing JSON fields:
328    ///
329    /// ```
330    /// # use postgrest::Postgrest;
331    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
332    /// # let client = Postgrest::new("https://your.postgrest.endpoint");
333    /// let resp = client
334    ///     .from("users")
335    ///     .select("id,json_data->phones->0->>number")
336    ///     .execute()
337    ///     .await?;
338    /// # Ok(())
339    /// # }
340    /// ```
341    ///
342    /// Embedded filters (assume there is a foreign key constraint between
343    /// tables `users` and `tweets`):
344    ///
345    /// ```
346    /// # use postgrest::Postgrest;
347    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
348    /// # let client = Postgrest::new("https://your.postgrest.endpoint");
349    /// let resp = client
350    ///     .from("users")
351    ///     .select("*,tweets(*)")
352    ///     .execute()
353    ///     .await?;
354    /// # Ok(())
355    /// # }
356    /// ```
357    pub fn select<T>(mut self, columns: T) -> Self
358    where
359        T: Into<String>,
360    {
361        self.queries.push(("select".to_string(), columns.into()));
362        self
363    }
364
365    /// Orders the result with the specified `columns`.
366    ///
367    /// # Example
368    ///
369    /// ```
370    /// use postgrest::Postgrest;
371    ///
372    /// let client = Postgrest::new("https://your.postgrest.endpoint");
373    /// client
374    ///     .from("users")
375    ///     .select("*")
376    ///     .order("username.desc.nullsfirst,age_range");
377    /// ```
378    pub fn order<T>(mut self, columns: T) -> Self
379    where
380        T: Into<String>,
381    {
382        self.queries.push(("order".to_string(), columns.into()));
383        self
384    }
385
386    /// Orders the result of a foreign table with the specified `columns`.
387    ///
388    /// # Example
389    ///
390    /// ```
391    /// use postgrest::Postgrest;
392    ///
393    /// let client = Postgrest::new("https://your.postgrest.endpoint");
394    /// client
395    ///     .from("countries")
396    ///     .select("name, cities(name)")
397    ///     .order_with_options("name", Some("cities"), true, false);
398    /// ```
399    pub fn order_with_options<T, U>(
400        mut self,
401        columns: T,
402        foreign_table: Option<U>,
403        ascending: bool,
404        nulls_first: bool,
405    ) -> Self
406    where
407        T: Into<String>,
408        U: Into<String>,
409    {
410        let mut key = "order".to_string();
411        if let Some(foreign_table) = foreign_table {
412            let foreign_table = foreign_table.into();
413            if !foreign_table.is_empty() {
414                key = format!("{}.order", foreign_table);
415            }
416        }
417
418        let mut ascending_string = "desc";
419        if ascending {
420            ascending_string = "asc";
421        }
422
423        let mut nulls_first_string = "nullslast";
424        if nulls_first {
425            nulls_first_string = "nullsfirst";
426        }
427
428        let existing_order = self.queries.iter().find(|(k, _)| k == &key);
429        match existing_order {
430            Some((_, v)) => {
431                let new_order = format!(
432                    "{},{}.{}.{}",
433                    v,
434                    columns.into(),
435                    ascending_string,
436                    nulls_first_string
437                );
438                self.queries.push((key, new_order));
439            }
440            None => {
441                self.queries.push((
442                    key,
443                    format!(
444                        "{}.{}.{}",
445                        columns.into(),
446                        ascending_string,
447                        nulls_first_string
448                    ),
449                ));
450            }
451        }
452        self
453    }
454
455    /// Limits the result with the specified `count`.
456    ///
457    /// # Example
458    ///
459    /// ```
460    /// use postgrest::Postgrest;
461    ///
462    /// let client = Postgrest::new("https://your.postgrest.endpoint");
463    /// client
464    ///     .from("users")
465    ///     .select("*")
466    ///     .limit(20);
467    /// ```
468    pub fn limit(mut self, count: usize) -> Self {
469        self.headers
470            .insert("Range-Unit", HeaderValue::from_static("items"));
471        self.headers.insert(
472            "Range",
473            HeaderValue::from_str(&format!("0-{}", count - 1)).unwrap(),
474        );
475        self
476    }
477
478    /// Limits the result of a foreign table with the specified `count`.
479    ///
480    /// # Example
481    ///
482    /// ```
483    /// use postgrest::Postgrest;
484    ///
485    /// let client = Postgrest::new("https://your.postgrest.endpoint");
486    /// client
487    ///     .from("countries")
488    ///     .select("name, cities(name)")
489    ///     .foreign_table_limit(1, "cities");
490    /// ```
491    pub fn foreign_table_limit<T>(mut self, count: usize, foreign_table: T) -> Self
492    where
493        T: Into<String>,
494    {
495        self.queries
496            .push((format!("{}.limit", foreign_table.into()), count.to_string()));
497        self
498    }
499
500    /// Limits the result to rows within the specified range, inclusive.
501    ///
502    /// # Example
503    ///
504    /// This retrieves the 2nd to 5th entries in the result:
505    /// ```
506    /// use postgrest::Postgrest;
507    ///
508    /// let client = Postgrest::new("https://your.postgrest.endpoint");
509    /// client
510    ///     .from("users")
511    ///     .select("*")
512    ///     .range(1, 4);
513    /// ```
514    pub fn range(mut self, low: usize, high: usize) -> Self {
515        self.headers
516            .insert("Range-Unit", HeaderValue::from_static("items"));
517        self.headers.insert(
518            "Range",
519            HeaderValue::from_str(&format!("{}-{}", low, high)).unwrap(),
520        );
521        self
522    }
523
524    fn count(mut self, method: &str) -> Self {
525        self.headers
526            .insert("Range-Unit", HeaderValue::from_static("items"));
527        // Value is irrelevant, we just want the size
528        self.headers
529            .insert("Range", HeaderValue::from_static("0-0"));
530        self.headers.insert(
531            "Prefer",
532            HeaderValue::from_str(&format!("count={}", method)).unwrap(),
533        );
534        self
535    }
536
537    /// Retrieves the (accurate) total size of the result.
538    ///
539    /// # Example
540    ///
541    /// ```
542    /// use postgrest::Postgrest;
543    ///
544    /// let client = Postgrest::new("https://your.postgrest.endpoint");
545    /// client
546    ///     .from("users")
547    ///     .select("*")
548    ///     .exact_count();
549    /// ```
550    pub fn exact_count(self) -> Self {
551        self.count("exact")
552    }
553
554    /// Estimates the total size of the result using PostgreSQL statistics. This
555    /// is faster than using `exact_count()`.
556    ///
557    /// # Example
558    ///
559    /// ```
560    /// use postgrest::Postgrest;
561    ///
562    /// let client = Postgrest::new("https://your.postgrest.endpoint");
563    /// client
564    ///     .from("users")
565    ///     .select("*")
566    ///     .planned_count();
567    /// ```
568    pub fn planned_count(self) -> Self {
569        self.count("planned")
570    }
571
572    /// Retrieves the total size of the result using some heuristics:
573    /// `exact_count` for smaller sizes, `planned_count` for larger sizes.
574    ///
575    /// # Example
576    ///
577    /// ```
578    /// use postgrest::Postgrest;
579    ///
580    /// let client = Postgrest::new("https://your.postgrest.endpoint");
581    /// client
582    ///     .from("users")
583    ///     .select("*")
584    ///     .estimated_count();
585    /// ```
586    pub fn estimated_count(self) -> Self {
587        self.count("estimated")
588    }
589
590    /// Retrieves only one row from the result.
591    ///
592    /// # Example
593    ///
594    /// ```
595    /// use postgrest::Postgrest;
596    ///
597    /// let client = Postgrest::new("https://your.postgrest.endpoint");
598    /// client
599    ///     .from("users")
600    ///     .select("*")
601    ///     .single();
602    /// ```
603    pub fn single(mut self) -> Self {
604        self.headers.insert(
605            "Accept",
606            HeaderValue::from_static("application/vnd.pgrst.object+json"),
607        );
608        self
609    }
610
611    /// Performs an INSERT of the `body` (in JSON) into the table.
612    ///
613    /// # Example
614    ///
615    /// ```
616    /// use postgrest::Postgrest;
617    ///
618    /// let client = Postgrest::new("https://your.postgrest.endpoint");
619    /// client
620    ///     .from("users")
621    ///     .insert(r#"[{ "username": "soedirgo", "status": "online" },
622    ///                 { "username": "jose", "status": "offline" }]"#);
623    /// ```
624    pub fn insert<T>(mut self, body: T) -> Self
625    where
626        T: Into<String>,
627    {
628        self.method = Method::POST;
629        self.headers
630            .insert("Prefer", HeaderValue::from_static("return=representation"));
631        self.body = Some(body.into());
632        self
633    }
634
635    /// Performs an upsert of the `body` (in JSON) into the table.
636    ///
637    /// # Note
638    ///
639    /// This merges duplicates by default. Ignoring duplicates is possible via
640    /// PostgREST, but is currently unsupported.
641    ///
642    /// # Example
643    ///
644    /// ```
645    /// use postgrest::Postgrest;
646    ///
647    /// let client = Postgrest::new("https://your.postgrest.endpoint");
648    /// client
649    ///     .from("users")
650    ///     .upsert(r#"[{ "username": "soedirgo", "status": "online" },
651    ///                 { "username": "jose", "status": "offline" }]"#);
652    /// ```
653    pub fn upsert<T>(mut self, body: T) -> Self
654    where
655        T: Into<String>,
656    {
657        self.method = Method::POST;
658        self.headers.insert(
659            "Prefer",
660            HeaderValue::from_static("return=representation,resolution=merge-duplicates"),
661        );
662        self.body = Some(body.into());
663        self
664    }
665
666    /// Resolve upsert conflicts on unique columns other than the primary key.
667    ///
668    /// # Note
669    ///
670    /// This informs PostgREST to resolve upsert conflicts through an
671    /// alternative, unique index other than the primary key of the table.
672    /// See the related
673    /// [PostgREST documentation](https://postgrest.org/en/stable/api.html?highlight=upsert#on-conflict).
674    ///
675    /// # Example
676    ///
677    /// ```
678    /// use postgrest::Postgrest;
679    ///
680    /// let client = Postgrest::new("https://your.postgrest.endpoint");
681    /// // Suppose `users` are keyed an SERIAL primary key,
682    /// // but have a unique index on `username`.
683    /// client
684    ///     .from("users")
685    ///     .upsert(r#"[{ "username": "soedirgo", "status": "online" },
686    ///                 { "username": "jose", "status": "offline" }]"#)
687    ///     .on_conflict("username");
688    /// ```
689    pub fn on_conflict<T>(mut self, columns: T) -> Self
690    where
691        T: Into<String>,
692    {
693        self.queries
694            .push(("on_conflict".to_string(), columns.into()));
695        self
696    }
697
698    /// Performs an UPDATE using the `body` (in JSON) on the table.
699    ///
700    /// # Example
701    ///
702    /// ```
703    /// use postgrest::Postgrest;
704    ///
705    /// let client = Postgrest::new("https://your.postgrest.endpoint");
706    /// client
707    ///     .from("users")
708    ///     .eq("username", "soedirgo")
709    ///     .update(r#"{ "status": "offline" }"#);
710    /// ```
711    pub fn update<T>(mut self, body: T) -> Self
712    where
713        T: Into<String>,
714    {
715        self.method = Method::PATCH;
716        self.headers
717            .insert("Prefer", HeaderValue::from_static("return=representation"));
718        self.body = Some(body.into());
719        self
720    }
721
722    /// Performs a DELETE on the table.
723    ///
724    /// # Example
725    ///
726    /// ```
727    /// use postgrest::Postgrest;
728    ///
729    /// let client = Postgrest::new("https://your.postgrest.endpoint");
730    /// client
731    ///     .from("users")
732    ///     .eq("username", "soedirgo")
733    ///     .delete();
734    /// ```
735    pub fn delete(mut self) -> Self {
736        self.method = Method::DELETE;
737        self.headers
738            .insert("Prefer", HeaderValue::from_static("return=representation"));
739        self
740    }
741
742    /// Performs a stored procedure call. This should only be used through the
743    /// `rpc()` method in `Postgrest`.
744    pub fn rpc<T>(mut self, params: T) -> Self
745    where
746        T: Into<String>,
747    {
748        self.method = Method::POST;
749        self.body = Some(params.into());
750        self.is_rpc = true;
751        self
752    }
753
754    /// Build the PostgREST request.
755    pub fn build(mut self) -> reqwest::RequestBuilder {
756        if let Some(schema) = self.schema {
757            let key = match self.method {
758                Method::GET | Method::HEAD => "Accept-Profile",
759                _ => "Content-Profile",
760            };
761            self.headers
762                .insert(key, HeaderValue::from_str(&schema).unwrap());
763        }
764        match self.method {
765            Method::GET | Method::HEAD => {}
766            _ => {
767                self.headers
768                    .insert("Content-Type", HeaderValue::from_static("application/json"));
769            }
770        };
771        self.client
772            .request(self.method, self.url)
773            .headers(self.headers)
774            .query(&self.queries)
775            .body(self.body.unwrap_or_default())
776    }
777
778    /// Executes the PostgREST request.
779    pub async fn execute(self) -> Result<Response, Error> {
780        self.build().send().await
781    }
782}
783
784#[cfg(test)]
785mod tests {
786    use super::*;
787
788    const TABLE_URL: &str = "http://localhost:3000/table";
789    const RPC_URL: &str = "http://localhost:3000/rpc";
790
791    #[test]
792    fn only_accept_json() {
793        let client = Client::new();
794        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client);
795        assert_eq!(
796            builder.headers.get("Accept").unwrap(),
797            HeaderValue::from_static("application/json")
798        );
799    }
800
801    #[test]
802    fn auth_with_token() {
803        let client = Client::new();
804        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).auth("$Up3rS3crET");
805        assert_eq!(
806            builder.headers.get("Authorization").unwrap(),
807            HeaderValue::from_static("Bearer $Up3rS3crET")
808        );
809    }
810
811    #[test]
812    fn select_assert_query() {
813        let client = Client::new();
814        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).select("some_table");
815        assert_eq!(builder.method, Method::GET);
816        assert_eq!(
817            builder
818                .queries
819                .contains(&("select".to_string(), "some_table".to_string())),
820            true
821        );
822    }
823
824    #[test]
825    fn order_assert_query() {
826        let client = Client::new();
827        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).order("id");
828        assert_eq!(
829            builder
830                .queries
831                .contains(&("order".to_string(), "id".to_string())),
832            true
833        );
834    }
835
836    #[test]
837    fn order_with_options_assert_query() {
838        let client = Client::new();
839        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).order_with_options(
840            "name",
841            Some("cities"),
842            true,
843            false,
844        );
845        assert_eq!(
846            builder
847                .queries
848                .contains(&("cities.order".to_string(), "name.asc.nullslast".to_string())),
849            true
850        );
851    }
852
853    #[test]
854    fn limit_assert_range_header() {
855        let client = Client::new();
856        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).limit(20);
857        assert_eq!(
858            builder.headers.get("Range").unwrap(),
859            HeaderValue::from_static("0-19")
860        );
861    }
862
863    #[test]
864    fn foreign_table_limit_assert_query() {
865        let client = Client::new();
866        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client)
867            .foreign_table_limit(20, "some_table");
868        assert_eq!(
869            builder
870                .queries
871                .contains(&("some_table.limit".to_string(), "20".to_string())),
872            true
873        );
874    }
875
876    #[test]
877    fn range_assert_range_header() {
878        let client = Client::new();
879        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).range(10, 20);
880        assert_eq!(
881            builder.headers.get("Range").unwrap(),
882            HeaderValue::from_static("10-20")
883        );
884    }
885
886    #[test]
887    fn single_assert_accept_header() {
888        let client = Client::new();
889        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).single();
890        assert_eq!(
891            builder.headers.get("Accept").unwrap(),
892            HeaderValue::from_static("application/vnd.pgrst.object+json")
893        );
894    }
895
896    #[test]
897    fn upsert_assert_prefer_header() {
898        let client = Client::new();
899        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).upsert("ignored");
900        assert_eq!(
901            builder.headers.get("Prefer").unwrap(),
902            HeaderValue::from_static("return=representation,resolution=merge-duplicates")
903        );
904    }
905
906    #[test]
907    fn not_rpc_should_not_have_flag() {
908        let client = Client::new();
909        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).select("ignored");
910        assert_eq!(builder.is_rpc, false);
911    }
912
913    #[test]
914    fn rpc_should_have_body_and_flag() {
915        let client = Client::new();
916        let builder =
917            Builder::new(RPC_URL, None, HeaderMap::new(), client).rpc("{\"a\": 1, \"b\": 2}");
918        assert_eq!(builder.body.unwrap(), "{\"a\": 1, \"b\": 2}");
919        assert_eq!(builder.is_rpc, true);
920    }
921
922    #[test]
923    fn chain_filters() -> Result<(), Box<dyn std::error::Error>> {
924        let client = Client::new();
925        let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client)
926            .eq("username", "supabot")
927            .neq("message", "hello world")
928            .gte("channel_id", "1")
929            .select("*");
930
931        let queries = builder.queries;
932        assert_eq!(queries.len(), 4);
933        assert!(queries.contains(&("username".into(), "eq.supabot".into())));
934        assert!(queries.contains(&("message".into(), "neq.hello world".into())));
935        assert!(queries.contains(&("channel_id".into(), "gte.1".into())));
936
937        Ok(())
938    }
939}