From 67e3552d705b9a59f0263522f611fdaf730d5e02 Mon Sep 17 00:00:00 2001 From: Anders Eie <1128648+strykejern@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:48:47 +0200 Subject: [PATCH] Added local copy of postgrest library to use temporarily Remove/replace after serde and json support is released on postgrest crate --- Cargo.toml | 6 +- src/external/mod.rs | 2 + src/external/postgrest_rs/builder.rs | 797 ++++++++++++++++++++++++++ src/external/postgrest_rs/filter.rs | 801 +++++++++++++++++++++++++++ src/external/postgrest_rs/mod.rs | 239 ++++++++ src/lib.rs | 7 +- src/postgrest.rs | 2 + 7 files changed, 1846 insertions(+), 8 deletions(-) create mode 100644 src/external/mod.rs create mode 100644 src/external/postgrest_rs/builder.rs create mode 100644 src/external/postgrest_rs/filter.rs create mode 100644 src/external/postgrest_rs/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 26446d0..c1c7884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "suparust" authors = ["Anders B. Eie"] -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "Apache-2.0 OR MIT" description = "Supabase client for Rust with support for WASM" @@ -16,15 +16,13 @@ serde = { version = "1.0.210", features = ["derive"] } thiserror = "1.0.64" tokio = { version = "1.40.0", features = ["sync"] } serde_json = "1.0.128" -postgrest = { version = "1.6.0" } #, features = ["serde"] } log = "0.4.22" supabase-auth = "0.9.1" mime = "0.3.17" [patch.crates-io] -# Custom git revision to get support for json in reqwest and serde in postgrest +# Custom git revision to get support for PartialEq on structs in supabase-auth # TODO: Update to proper version when it's released -postgrest = { git = "https://github.com/strykejern/postgrest-rs.git", branch = "serde-default" } supabase-auth = { git = "https://github.com/strykejern/supabase-auth-rs", branch = "derives" } [target.'cfg(target_family = "wasm")'.dependencies] diff --git a/src/external/mod.rs b/src/external/mod.rs new file mode 100644 index 0000000..9de7a92 --- /dev/null +++ b/src/external/mod.rs @@ -0,0 +1,2 @@ +#[cfg(not(doctest))] +pub(crate) mod postgrest_rs; diff --git a/src/external/postgrest_rs/builder.rs b/src/external/postgrest_rs/builder.rs new file mode 100644 index 0000000..e9c4b18 --- /dev/null +++ b/src/external/postgrest_rs/builder.rs @@ -0,0 +1,797 @@ +//! # Copyright +//! +//! This file is copied from the [postgrest crate](https://crates.io/crates/postgrest) ([repository](https://github.com/supabase-community/postgrest-rs)). +//! +//! It is then modified to fit this project. +//! +//! ## License +//! MIT License +//! +//! Copyright (c) 2020 Supabase +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. +//! + +use reqwest::{ + header::{HeaderMap, HeaderValue}, + Client, Error, Method, Response, +}; + +/// QueryBuilder struct +#[derive(Clone, Debug)] +pub struct Builder { + method: Method, + url: String, + schema: Option, + // Need this to allow access from `filter.rs` + pub(crate) queries: Vec<(String, String)>, + headers: HeaderMap, + body: Option, + is_rpc: bool, + // sharing a client is a good idea, performance wise + // the client has to live at least as much as the builder + client: Client, +} + +// TODO: Test Unicode support +impl Builder { + /// Creates a new `Builder` with the specified `schema`. + pub fn new(url: T, schema: Option, headers: HeaderMap, client: Client) -> Self + where + T: Into, + { + let url = url.into().trim_end_matches('/').to_string(); + + let mut builder = Builder { + method: Method::GET, + url, + schema, + queries: Vec::new(), + headers, + body: None, + is_rpc: false, + client, + }; + builder + .headers + .insert("Accept", HeaderValue::from_static("application/json")); + builder + } + + /// Authenticates the request with JWT. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("table") + /// .auth("supers.ecretjw.ttoken"); + /// ``` + pub fn auth(mut self, token: T) -> Self + where + T: AsRef, + { + self.headers.insert( + "Authorization", + HeaderValue::from_str(&format!("Bearer {}", token.as_ref())).unwrap(), + ); + self + } + + /// Performs horizontal filtering with SELECT. + /// + /// # Note + /// + /// `columns` is whitespace-sensitive, so you need to omit them unless your + /// column name contains whitespaces. + /// + /// # Example + /// + /// Simple example: + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// let resp = client + /// .from("table") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// Renaming columns: + /// + /// ``` + /// # use postgrest::Postgrest; + /// # async fn run() -> Result<(), Box> { + /// # let client = Postgrest::new("https://your.postgrest.endpoint"); + /// let resp = client + /// .from("users") + /// .select("name:very_very_long_column_name") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// Casting columns: + /// + /// ``` + /// # use postgrest::Postgrest; + /// # async fn run() -> Result<(), Box> { + /// # let client = Postgrest::new("https://your.postgrest.endpoint"); + /// let resp = client + /// .from("users") + /// .select("age::text") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// SELECTing JSON fields: + /// + /// ``` + /// # use postgrest::Postgrest; + /// # async fn run() -> Result<(), Box> { + /// # let client = Postgrest::new("https://your.postgrest.endpoint"); + /// let resp = client + /// .from("users") + /// .select("id,json_data->phones->0->>number") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// Embedded filters (assume there is a foreign key constraint between + /// tables `users` and `tweets`): + /// + /// ``` + /// # use postgrest::Postgrest; + /// # async fn run() -> Result<(), Box> { + /// # let client = Postgrest::new("https://your.postgrest.endpoint"); + /// let resp = client + /// .from("users") + /// .select("*,tweets(*)") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn select(mut self, columns: T) -> Self + where + T: Into, + { + self.queries.push(("select".to_string(), columns.into())); + self + } + + /// Orders the result with the specified `columns`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .order("username.desc.nullsfirst,age_range"); + /// ``` + pub fn order(mut self, columns: T) -> Self + where + T: Into, + { + self.queries.push(("order".to_string(), columns.into())); + self + } + + /// Orders the result of a foreign table with the specified `columns`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("countries") + /// .select("name, cities(name)") + /// .order_with_options("name", Some("cities"), true, false); + /// ``` + pub fn order_with_options( + mut self, + columns: T, + foreign_table: Option, + ascending: bool, + nulls_first: bool, + ) -> Self + where + T: Into, + U: Into, + { + let mut key = "order".to_string(); + if let Some(foreign_table) = foreign_table { + let foreign_table = foreign_table.into(); + if !foreign_table.is_empty() { + key = format!("{}.order", foreign_table); + } + } + + let mut ascending_string = "desc"; + if ascending { + ascending_string = "asc"; + } + + let mut nulls_first_string = "nullslast"; + if nulls_first { + nulls_first_string = "nullsfirst"; + } + + let existing_order = self.queries.iter().find(|(k, _)| k == &key); + match existing_order { + Some((_, v)) => { + let new_order = format!( + "{},{}.{}.{}", + v, + columns.into(), + ascending_string, + nulls_first_string + ); + self.queries.push((key, new_order)); + } + None => { + self.queries.push(( + key, + format!( + "{}.{}.{}", + columns.into(), + ascending_string, + nulls_first_string + ), + )); + } + } + self + } + + /// Limits the result with the specified `count`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .limit(20); + /// ``` + pub fn limit(mut self, count: usize) -> Self { + self.headers + .insert("Range-Unit", HeaderValue::from_static("items")); + self.headers.insert( + "Range", + HeaderValue::from_str(&format!("0-{}", count - 1)).unwrap(), + ); + self + } + + /// Limits the result of a foreign table with the specified `count`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("countries") + /// .select("name, cities(name)") + /// .foreign_table_limit(1, "cities"); + /// ``` + pub fn foreign_table_limit(mut self, count: usize, foreign_table: T) -> Self + where + T: Into, + { + self.queries + .push((format!("{}.limit", foreign_table.into()), count.to_string())); + self + } + + /// Limits the result to rows within the specified range, inclusive. + /// + /// # Example + /// + /// This retrieves the 2nd to 5th entries in the result: + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .range(1, 4); + /// ``` + pub fn range(mut self, low: usize, high: usize) -> Self { + self.headers + .insert("Range-Unit", HeaderValue::from_static("items")); + self.headers.insert( + "Range", + HeaderValue::from_str(&format!("{}-{}", low, high)).unwrap(), + ); + self + } + + fn count(mut self, method: &str) -> Self { + self.headers + .insert("Range-Unit", HeaderValue::from_static("items")); + // Value is irrelevant, we just want the size + self.headers + .insert("Range", HeaderValue::from_static("0-0")); + self.headers.insert( + "Prefer", + HeaderValue::from_str(&format!("count={}", method)).unwrap(), + ); + self + } + + /// Retrieves the (accurate) total size of the result. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .exact_count(); + /// ``` + pub fn exact_count(self) -> Self { + self.count("exact") + } + + /// Estimates the total size of the result using PostgreSQL statistics. This + /// is faster than using `exact_count()`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .planned_count(); + /// ``` + pub fn planned_count(self) -> Self { + self.count("planned") + } + + /// Retrieves the total size of the result using some heuristics: + /// `exact_count` for smaller sizes, `planned_count` for larger sizes. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .estimated_count(); + /// ``` + pub fn estimated_count(self) -> Self { + self.count("estimated") + } + + /// Retrieves only one row from the result. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .select("*") + /// .single(); + /// ``` + pub fn single(mut self) -> Self { + self.headers.insert( + "Accept", + HeaderValue::from_static("application/vnd.pgrst.object+json"), + ); + self + } + + /// Performs an INSERT of the `body` (in JSON) into the table. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// #[derive(serde::Serialize)] + /// struct MyStruct {} + /// + /// let my_serializable_struct = MyStruct {}; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .insert(&my_serializable_struct).unwrap(); + /// ``` + pub fn insert(self, body: &T) -> serde_json::Result + where + T: serde::Serialize, + { + Ok(self.insert_impl(serde_json::to_string(body)?)) + } + + fn insert_impl(mut self, body: String) -> Self { + self.method = Method::POST; + self.headers + .insert("Prefer", HeaderValue::from_static("return=representation")); + self.body = Some(body); + self + } + + /// Performs an upsert of the `body` (in JSON) into the table. + /// + /// # Note + /// + /// This merges duplicates by default. Ignoring duplicates is possible via + /// PostgREST, but is currently unsupported. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// #[derive(serde::Serialize)] + /// struct MyStruct {} + /// + /// let my_serializable_struct = MyStruct {}; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .upsert(&my_serializable_struct).unwrap(); + /// ``` + pub fn upsert(self, body: &T) -> serde_json::Result + where + T: serde::Serialize, + { + Ok(self.upsert_impl(serde_json::to_string(body)?)) + } + + fn upsert_impl(mut self, body: String) -> Self { + self.method = Method::POST; + self.headers.insert( + "Prefer", + HeaderValue::from_static("return=representation,resolution=merge-duplicates"), + ); + self.body = Some(body); + self + } + + /// Resolve upsert conflicts on unique columns other than the primary key. + /// + /// # Note + /// + /// This informs PostgREST to resolve upsert conflicts through an + /// alternative, unique index other than the primary key of the table. + /// See the related + /// [PostgREST documentation](https://postgrest.org/en/stable/api.html?highlight=upsert#on-conflict). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// // Suppose `users` are keyed an SERIAL primary key, + /// // but have a unique index on `username`. + /// #[derive(serde::Serialize)] + /// struct MyStruct {} + /// + /// let my_serializable_struct = MyStruct {}; + /// + /// client + /// .from("users") + /// .upsert(&my_serializable_struct).unwrap(); + /// ``` + pub fn on_conflict(mut self, columns: T) -> Self + where + T: Into, + { + self.queries + .push(("on_conflict".to_string(), columns.into())); + self + } + + /// Performs an UPDATE using the `body` (in JSON) on the table. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// #[derive(serde::Serialize)] + /// struct MyStruct {} + /// + /// let my_serializable_struct = MyStruct {}; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .eq("username", "soedirgo") + /// .update(&my_serializable_struct).unwrap(); + /// ``` + pub fn update(self, body: &T) -> serde_json::Result + where + T: serde::Serialize, + { + Ok(self.update_impl(serde_json::to_string(body)?)) + } + + fn update_impl(mut self, body: String) -> Self { + self.method = Method::PATCH; + self.headers + .insert("Prefer", HeaderValue::from_static("return=representation")); + self.body = Some(body); + self + } + + /// Performs a DELETE on the table. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint"); + /// client + /// .from("users") + /// .eq("username", "soedirgo") + /// .delete(); + /// ``` + pub fn delete(mut self) -> Self { + self.method = Method::DELETE; + self.headers + .insert("Prefer", HeaderValue::from_static("return=representation")); + self + } + + /// Performs a stored procedure call. This should only be used through the + /// `rpc()` method in `Postgrest`. + pub fn rpc(mut self, params: T) -> Self + where + T: Into, + { + self.method = Method::POST; + self.body = Some(params.into()); + self.is_rpc = true; + self + } + + /// Build the PostgREST request. + pub fn build(mut self) -> reqwest::RequestBuilder { + if let Some(schema) = self.schema { + let key = match self.method { + Method::GET | Method::HEAD => "Accept-Profile", + _ => "Content-Profile", + }; + self.headers + .insert(key, HeaderValue::from_str(&schema).unwrap()); + } + match self.method { + Method::GET | Method::HEAD => {} + _ => { + self.headers + .insert("Content-Type", HeaderValue::from_static("application/json")); + } + }; + self.client + .request(self.method, self.url) + .headers(self.headers) + .query(&self.queries) + .body(self.body.unwrap_or_default()) + } + + /// Executes the PostgREST request. + pub async fn execute(self) -> Result { + self.build().send().await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TABLE_URL: &str = "http://localhost:3000/table"; + const RPC_URL: &str = "http://localhost:3000/rpc"; + + #[test] + fn only_accept_json() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client); + assert_eq!( + builder.headers.get("Accept").unwrap(), + HeaderValue::from_static("application/json") + ); + } + + #[test] + fn auth_with_token() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).auth("$Up3rS3crET"); + assert_eq!( + builder.headers.get("Authorization").unwrap(), + HeaderValue::from_static("Bearer $Up3rS3crET") + ); + } + + #[test] + fn select_assert_query() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).select("some_table"); + assert_eq!(builder.method, Method::GET); + assert_eq!( + builder + .queries + .contains(&("select".to_string(), "some_table".to_string())), + true + ); + } + + #[test] + fn order_assert_query() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).order("id"); + assert_eq!( + builder + .queries + .contains(&("order".to_string(), "id".to_string())), + true + ); + } + + #[test] + fn order_with_options_assert_query() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).order_with_options( + "name", + Some("cities"), + true, + false, + ); + assert_eq!( + builder + .queries + .contains(&("cities.order".to_string(), "name.asc.nullslast".to_string())), + true + ); + } + + #[test] + fn limit_assert_range_header() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).limit(20); + assert_eq!( + builder.headers.get("Range").unwrap(), + HeaderValue::from_static("0-19") + ); + } + + #[test] + fn foreign_table_limit_assert_query() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client) + .foreign_table_limit(20, "some_table"); + assert_eq!( + builder + .queries + .contains(&("some_table.limit".to_string(), "20".to_string())), + true + ); + } + + #[test] + fn range_assert_range_header() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).range(10, 20); + assert_eq!( + builder.headers.get("Range").unwrap(), + HeaderValue::from_static("10-20") + ); + } + + #[test] + fn single_assert_accept_header() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).single(); + assert_eq!( + builder.headers.get("Accept").unwrap(), + HeaderValue::from_static("application/vnd.pgrst.object+json") + ); + } + + #[test] + fn upsert_assert_prefer_header_serde() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client) + .upsert(&()) + .unwrap(); + assert_eq!( + builder.headers.get("Prefer").unwrap(), + HeaderValue::from_static("return=representation,resolution=merge-duplicates") + ); + } + + #[test] + fn not_rpc_should_not_have_flag() { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client).select("ignored"); + assert_eq!(builder.is_rpc, false); + } + + #[test] + fn rpc_should_have_body_and_flag() { + let client = Client::new(); + let builder = + Builder::new(RPC_URL, None, HeaderMap::new(), client).rpc("{\"a\": 1, \"b\": 2}"); + assert_eq!(builder.body.unwrap(), "{\"a\": 1, \"b\": 2}"); + assert_eq!(builder.is_rpc, true); + } + + #[test] + fn chain_filters() -> Result<(), Box> { + let client = Client::new(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new(), client) + .eq("username", "supabot") + .neq("message", "hello world") + .gte("channel_id", "1") + .select("*"); + + let queries = builder.queries; + assert_eq!(queries.len(), 4); + assert!(queries.contains(&("username".into(), "eq.supabot".into()))); + assert!(queries.contains(&("message".into(), "neq.hello world".into()))); + assert!(queries.contains(&("channel_id".into(), "gte.1".into()))); + + Ok(()) + } +} diff --git a/src/external/postgrest_rs/filter.rs b/src/external/postgrest_rs/filter.rs new file mode 100644 index 0000000..2c9d11d --- /dev/null +++ b/src/external/postgrest_rs/filter.rs @@ -0,0 +1,801 @@ +//! # Copyright +//! +//! This file is copied from the [postgrest crate](https://crates.io/crates/postgrest) ([repository](https://github.com/supabase-community/postgrest-rs)). +//! +//! It is then modified to fit this project. +//! +//! ## License +//! MIT License +//! +//! Copyright (c) 2020 Supabase +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. +//! + +use super::Builder; + +impl Builder { + /// Finds all rows which doesn't satisfy the filter. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .not("eq", "name", "New Zealand") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn not(mut self, operator: T, column: U, filter: V) -> Self + where + T: AsRef, + U: AsRef, + V: AsRef, + { + self.queries.push(( + column.as_ref().into(), + format!("not.{}.{}", operator.as_ref(), filter.as_ref()), + )); + self + } + + /// Finds all rows satisfying all of the filters. + /// + /// # Note + /// + /// If your column/filter contains PostgREST's reserved characters, you need + /// to surround them with double quotes (not percent encoded). See + /// [here](https://postgrest.org/en/v7.0.0/api.html#reserved-characters) for + /// details. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .and("name.eq.New Zealand,or(id.gte.1,capital.is.null)") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn and(mut self, filters: T) -> Self + where + T: AsRef, + { + self.queries + .push(("and".to_string(), format!("({})", filters.as_ref()))); + self + } + + /// Finds all rows satisfying at least one of the filters. + /// + /// # Note + /// + /// If your column/filter contains PostgREST's reserved characters, you need + /// to surround them with double quotes (not percent encoded). See + /// [here](https://postgrest.org/en/v7.0.0/api.html#reserved-characters) for + /// details. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .or("name.eq.New Zealand,or(id.gte.1,capital.is.null)") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn or(mut self, filters: T) -> Self + where + T: AsRef, + { + self.queries + .push(("or".to_string(), format!("({})", filters.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` exactly matches the + /// specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .eq("name", "New Zealand") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn eq(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("eq.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` doesn't match the + /// specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .neq("name", "China") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn neq(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("neq.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` is greater than the + /// specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .gt("id", "20") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn gt(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("gt.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` is greater than or + /// equal to the specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .gte("id", "20") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn gte(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("gte.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` is less than the + /// specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .lt("id", "20") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn lt(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("lt.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` is less than or equal + /// to the specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .lte("id", "20") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn lte(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("lte.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value in the stated `column` matches the supplied + /// `pattern` (case sensitive). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .like("name", "%United%") + /// .select("*") + /// .execute() + /// .await?; + /// + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .like("name", "%United States%") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn like(mut self, column: T, pattern: U) -> Self + where + T: AsRef, + U: Into, + { + let pattern = pattern.into().replace('%', "*"); + self.queries + .push((column.as_ref().into(), format!("like.{}", pattern))); + self + } + + /// Finds all rows whose value in the stated `column` matches the supplied + /// `pattern` (case insensitive). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .ilike("name", "%United%") + /// .select("*") + /// .execute() + /// .await?; + /// + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .ilike("name", "%united states%") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn ilike(mut self, column: T, pattern: U) -> Self + where + T: AsRef, + U: Into, + { + let pattern = pattern.into().replace('%', "*"); + self.queries + .push((column.as_ref().into(), format!("ilike.{}", pattern))); + self + } + + /// A check for exact equality (null, true, false), finds all rows whose + /// value on the stated `column` exactly match the specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .is("name", "null") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn is(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("is.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose value on the stated `column` is found on the + /// specified `values`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .in_("name", vec!["China", "France"]) + /// .select("*") + /// .execute() + /// .await?; + /// + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("countries") + /// .in_("capitals", vec!["Beijing,China", "Paris,France"]) + /// .select("*") + /// .execute() + /// .await?; + /// + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("recipes") + /// .in_("food_supplies", vec!["carrot (big)", "carrot (small)"]) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn in_(mut self, column: T, values: U) -> Self + where + T: AsRef, + U: IntoIterator, + V: AsRef, + { + let mut values: String = values + .into_iter() + .fold(String::new(), |a, s| a + s.as_ref() + ","); + values.pop(); + self.queries + .push((column.as_ref().into(), format!("in.({})", values))); + self + } + + /// Finds all rows whose json, array, or range value on the stated `column` + /// contains the values specified in `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("users") + /// .cs("age_range", "(10,20)") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn cs(mut self, column: T, filter: U) -> Self + where + T: AsRef, + U: AsRef, + { + self.queries + .push((column.as_ref().into(), format!("cs.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose json, array, or range value on the stated `column` + /// is contained by the specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("users") + /// .cd("age_range", "(10,20)") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn cd(mut self, column: T, filter: U) -> Self + where + T: Into, + U: AsRef, + { + self.queries + .push((column.into(), format!("cd.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose range value on the stated `column` is strictly to + /// the left of the specified `range`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("users") + /// .sl("age_range", (10, 20)) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn sl(mut self, column: T, range: (i64, i64)) -> Self + where + T: Into, + { + self.queries + .push((column.into(), format!("sl.({},{})", range.0, range.1))); + self + } + + /// Finds all rows whose range value on the stated `column` is strictly to + /// the right of the specified `range`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .sr("age_range", (10, 20)) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn sr(mut self, column: T, range: (i64, i64)) -> Self + where + T: Into, + { + self.queries + .push((column.into(), format!("sr.({},{})", range.0, range.1))); + self + } + + /// Finds all rows whose range value on the stated `column` does not extend + /// to the left of the specified `range`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .nxl("age_range", (10, 20)) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn nxl(mut self, column: T, range: (i64, i64)) -> Self + where + T: Into, + { + self.queries + .push((column.into(), format!("nxl.({},{})", range.0, range.1))); + self + } + + /// Finds all rows whose range value on the stated `column` does not extend + /// to the right of the specified `range`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .nxr("age_range", (10, 20)) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn nxr(mut self, column: T, range: (i64, i64)) -> Self + where + T: Into, + { + self.queries + .push((column.into(), format!("nxr.({},{})", range.0, range.1))); + self + } + + /// Finds all rows whose range value on the stated `column` is adjacent to + /// the specified `range`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .adj("age_range", (10, 20)) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn adj(mut self, column: T, range: (i64, i64)) -> Self + where + T: Into, + { + self.queries + .push((column.into(), format!("adj.({},{})", range.0, range.1))); + self + } + + /// Finds all rows whose array or range value on the stated `column` + /// overlaps with the specified `filter`. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .ov("age_range", "(10,20)") + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn ov(mut self, column: T, filter: U) -> Self + where + T: Into, + U: AsRef, + { + self.queries + .push((column.into(), format!("ov.{}", filter.as_ref()))); + self + } + + /// Finds all rows whose tsvector value on the stated `column` matches + /// to_tsquery(`tsquery`). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .fts("phrase", "The Fat Cats", Some("english")) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn fts(mut self, column: T, tsquery: U, config: Option<&str>) -> Self + where + T: Into, + U: AsRef, + { + let config = if let Some(conf) = config { + format!("({})", conf) + } else { + String::new() + }; + self.queries + .push((column.into(), format!("fts{}.{}", config, tsquery.as_ref()))); + self + } + + /// Finds all rows whose tsvector value on the stated `column` matches + /// plainto_tsquery(`tsquery`). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .plfts("phrase", "The Fat Cats", None) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn plfts(mut self, column: T, tsquery: U, config: Option<&str>) -> Self + where + T: Into, + U: AsRef, + { + let config = if let Some(conf) = config { + format!("({})", conf) + } else { + String::new() + }; + self.queries.push(( + column.into(), + format!("plfts{}.{}", config, tsquery.as_ref()), + )); + self + } + + /// Finds all rows whose tsvector value on the stated `column` matches + /// phraseto_tsquery(`tsquery`). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .phfts("phrase", "The Fat Cats", Some("english")) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn phfts(mut self, column: T, tsquery: U, config: Option<&str>) -> Self + where + T: Into, + U: AsRef, + { + let config = if let Some(conf) = config { + format!("({})", conf) + } else { + String::new() + }; + self.queries.push(( + column.into(), + format!("phfts{}.{}", config, tsquery.as_ref()), + )); + self + } + + /// Finds all rows whose tsvector value on the stated `column` matches + /// websearch_to_tsquery(`tsquery`). + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// # async fn run() -> Result<(), Box> { + /// let resp = Postgrest::new("http://localhost:3000") + /// .from("table") + /// .wfts("phrase", "The Fat Cats", None) + /// .select("*") + /// .execute() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn wfts(mut self, column: T, tsquery: U, config: Option<&str>) -> Self + where + T: Into, + U: AsRef, + { + let config = if let Some(conf) = config { + format!("({})", conf) + } else { + String::new() + }; + self.queries.push(( + column.into(), + format!("wfts{}.{}", config, tsquery.as_ref()), + )); + self + } +} diff --git a/src/external/postgrest_rs/mod.rs b/src/external/postgrest_rs/mod.rs new file mode 100644 index 0000000..dfb746e --- /dev/null +++ b/src/external/postgrest_rs/mod.rs @@ -0,0 +1,239 @@ +//! # Copyright +//! +//! This file is copied from the [postgrest crate](https://crates.io/crates/postgrest) ([repository](https://github.com/supabase-community/postgrest-rs)). +//! +//! It is then modified to fit this project. +//! +//! ## License +//! MIT License +//! +//! Copyright (c) 2020 Supabase +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. +//! +//! # postgrest-rs +//! +//! [PostgREST][postgrest] client-side library. +//! +//! This library is a thin wrapper that brings an ORM-like interface to +//! PostgREST. +//! +//! ## Usage +//! +//! Simple example: +//! ``` +//! use postgrest::Postgrest; +//! +//! # async fn run() -> Result<(), Box> { +//! let client = Postgrest::new("https://your.postgrest.endpoint"); +//! let resp = client +//! .from("your_table") +//! .select("*") +//! .execute() +//! .await?; +//! let body = resp +//! .text() +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! Using filters: +//! ``` +//! # use postgrest::Postgrest; +//! # async fn run() -> Result<(), Box> { +//! # let client = Postgrest::new("https://your.postgrest.endpoint"); +//! let resp = client +//! .from("countries") +//! .eq("name", "Germany") +//! .gte("id", "20") +//! .select("*") +//! .execute() +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! Updating a table: +//! ``` +//! # use postgrest::Postgrest; +//! # #[cfg(not(feature = "serde"))] +//! # async fn run() -> Result<(), Box> { +//! # let client = Postgrest::new("https://your.postgrest.endpoint"); +//! let resp = client +//! .from("users") +//! .eq("username", "soedirgo") +//! .update("{\"organization\": \"supabase\"}") +//! .execute() +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! Executing stored procedures: +//! ``` +//! # use postgrest::Postgrest; +//! # async fn run() -> Result<(), Box> { +//! # let client = Postgrest::new("https://your.postgrest.endpoint"); +//! let resp = client +//! .rpc("add", r#"{"a": 1, "b": 2}"#) +//! .execute() +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! Check out the [README][readme] for more info. +//! +//! [postgrest]: https://postgrest.org +//! [readme]: https://github.com/supabase/postgrest-rs + +mod builder; +mod filter; + +pub use builder::Builder; +use reqwest::header::{HeaderMap, HeaderValue, IntoHeaderName}; +use reqwest::Client; + +#[derive(Clone, Debug)] +pub struct Postgrest { + url: String, + schema: Option, + headers: HeaderMap, + client: Client, +} + +impl Postgrest { + /// Creates a Postgrest client. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("http://your.postgrest.endpoint"); + /// ``` + pub fn new(url: T) -> Self + where + T: Into, + { + Postgrest { + url: url.into(), + schema: None, + headers: HeaderMap::new(), + client: Client::new(), + } + } + + /// Add arbitrary headers to the request. For instance when you may want to connect + /// through an API gateway that needs an API key header. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("https://your.postgrest.endpoint") + /// .insert_header("apikey", "super.secret.key") + /// .from("table"); + /// ``` + pub fn insert_header( + mut self, + header_name: impl IntoHeaderName, + header_value: impl AsRef, + ) -> Self { + self.headers.insert( + header_name, + HeaderValue::from_str(header_value.as_ref()).expect("Invalid header value."), + ); + self + } + + /// Perform a table operation. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("http://your.postgrest.endpoint"); + /// client.from("table"); + /// ``` + pub fn from(&self, table: T) -> Builder + where + T: AsRef, + { + let url = format!("{}/{}", self.url, table.as_ref()); + Builder::new( + url, + self.schema.clone(), + self.headers.clone(), + self.client.clone(), + ) + } + + /// Perform a stored procedure call. + /// + /// # Example + /// + /// ``` + /// use postgrest::Postgrest; + /// + /// let client = Postgrest::new("http://your.postgrest.endpoint"); + /// client.rpc("multiply", r#"{"a": 1, "b": 2}"#); + /// ``` + pub fn rpc(&self, function: T, params: U) -> Builder + where + T: AsRef, + U: Into, + { + let url = format!("{}/rpc/{}", self.url, function.as_ref()); + Builder::new( + url, + self.schema.clone(), + self.headers.clone(), + self.client.clone(), + ) + .rpc(params) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const REST_URL: &str = "http://localhost:3000"; + + #[test] + fn initialize() { + assert_eq!(Postgrest::new(REST_URL).url, REST_URL); + } + + #[test] + fn with_insert_header() { + assert_eq!( + Postgrest::new(REST_URL) + .insert_header("apikey", "super.secret.key") + .headers + .get("apikey") + .unwrap(), + "super.secret.key" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1425de2..924492c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ //! # } mod auth; +mod external; mod postgrest; pub mod storage; #[cfg(test)] @@ -41,8 +42,6 @@ use std::sync::Arc; pub use supabase_auth::models::{LogoutScope, Session, User}; use tokio::sync::RwLock; -extern crate postgrest as external_postgrest; - pub type Result = std::result::Result; #[derive(Clone)] @@ -50,7 +49,7 @@ pub struct Supabase { auth: Arc, session: Arc>>, session_listener: SessionChangeListener, - postgrest: Arc>, + postgrest: Arc>, storage_client: reqwest::Client, api_key: String, url_base: String, @@ -84,7 +83,7 @@ impl Supabase { session: Option, session_listener: SessionChangeListener, ) -> Self { - let mut postgrest = external_postgrest::Postgrest::new(format!("{url}/rest/v1")) + let mut postgrest = external::postgrest_rs::Postgrest::new(format!("{url}/rest/v1")) .insert_header("apikey", api_key); if let Some(session) = &session { diff --git a/src/postgrest.rs b/src/postgrest.rs index c6c1545..62dc2f6 100644 --- a/src/postgrest.rs +++ b/src/postgrest.rs @@ -1,6 +1,8 @@ use crate::Result; use crate::Supabase; +use crate::external::postgrest_rs as postgrest; + impl Supabase { pub async fn from(&self, table: T) -> Result where