diff --git a/README.md b/README.md index cdd9aa3..78fb997 100644 --- a/README.md +++ b/README.md @@ -49,31 +49,26 @@ let body = resp .await?; ``` -Simplified example using an authenticated API gateway and JWT authorization (like SupaBase). +Simplified example using a custom header (e.g. for API gateway authentication with Supabase). ```rust use postgrest::Postgrest; -static HEADER_KEY: &str = "apikey"; -let header_value: String = "ExampleAPIKeyValue".to_string(); // EXAMPLE ONLY! +let client = Postgrest::new("https://your.supabase.endpoint/rest/v1/") + .insert_header("apikey", "ExampleAPIKeyValue"); // EXAMPLE ONLY! // Don't actually hard code this value, that's really bad. Use environment // variables like with the dotenv(https://crates.io/crates/dotenv) crate to inject - -let client = Postgrest::new("https://your.supabase.endpoint/rest/v1/"); let resp = client .from("your_table") - .auth(header_value) - .insert_header(HEADER_KEY, header_value) .select("*") .execute() .await?; let body = resp .text() .await?; - ``` -**Secure** example with authenticated API gateway and JWT authorization using the dotenv crate to correctly retrieve sensitive values. +**Secure** example with authenticated API gateway using the dotenv crate to correctly retrieve sensitive values. ```rust use postgrest::Postgrest; @@ -81,22 +76,18 @@ use dotenv; dotenv().ok(); -static HEADER_KEY: &str = "apikey"; - let client = Postgrest::new("https://your.supabase.endpoint/rest/v1/"); + .insert_header( + "apikey", + env::var("SUPABASE_PUBLIC_API_KEY").unwrap()) let resp = client .from("your_table") - .auth(env::var("supabase_public_api_key").unwrap().to_string())) - .insert_header( - HEADER_KEY, - env::var("supabase_public_api_key").unwrap().to_string()) .select("*") .execute() .await?; let body = resp .text() .await?; - ``` ### Building Queries diff --git a/src/builder.rs b/src/builder.rs index 160b15e..0345abd 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -17,7 +17,7 @@ pub struct Builder { // TODO: Test Unicode support impl Builder { /// Creates a new `Builder` with the specified `schema`. - pub fn new(url: T, schema: Option) -> Self + pub fn new(url: T, schema: Option, headers: HeaderMap) -> Self where T: Into, { @@ -26,7 +26,7 @@ impl Builder { url: url.into(), schema, queries: Vec::new(), - headers: HeaderMap::new(), + headers, body: None, is_rpc: false, }; @@ -59,31 +59,6 @@ impl Builder { self } - /// Add arbitrary headers to the request. For instance when you may want to connect - /// through an api gateway that needs an API key header in addition to the JWT. - /// - /// # Example - /// - /// ``` - /// use postgrest::Postgrest; - /// - /// static header_name: &'static str = "foo"; - /// let header_value: String = "bar".to_string(); - /// - /// let client = Postgrest::new("https://your.postgrest.endpoint/rest/v1/"); - /// client - /// .from("table") - /// .insert_header(header_name, header_value); - /// ``` - pub fn insert_header(mut self, header_name: &'static str, header_value: String) -> Self { - self.headers.insert( - header_name, - HeaderValue::from_str(&header_value) - .expect("Couldn't convert supplied header value from String to str."), - ); - self - } - /// Performs horizontal filtering with SELECT. /// /// # Note @@ -478,7 +453,7 @@ mod tests { #[test] fn only_accept_json() { - let builder = Builder::new(TABLE_URL, None); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()); assert_eq!( builder.headers.get("Accept").unwrap(), HeaderValue::from_static("application/json") @@ -487,27 +462,16 @@ mod tests { #[test] fn auth_with_token() { - let builder = Builder::new(TABLE_URL, None).auth("$Up3rS3crET"); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).auth("$Up3rS3crET"); assert_eq!( builder.headers.get("Authorization").unwrap(), HeaderValue::from_static("Bearer $Up3rS3crET") ); } - #[test] - fn with_insert_header() { - static A_HEADER_KEY: &str = "foo"; - let a_header_value: String = "bar".to_string(); - let builder = Builder::new(TABLE_URL, None).insert_header(A_HEADER_KEY, a_header_value); - assert_eq!( - builder.headers.get("foo").unwrap(), - HeaderValue::from_static("bar") - ); - } - #[test] fn select_assert_query() { - let builder = Builder::new(TABLE_URL, None).select("some_table"); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).select("some_table"); assert_eq!(builder.method, Method::GET); assert_eq!( builder @@ -519,7 +483,7 @@ mod tests { #[test] fn order_assert_query() { - let builder = Builder::new(TABLE_URL, None).order("id"); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).order("id"); assert_eq!( builder .queries @@ -530,7 +494,7 @@ mod tests { #[test] fn limit_assert_range_header() { - let builder = Builder::new(TABLE_URL, None).limit(20); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).limit(20); assert_eq!( builder.headers.get("Range").unwrap(), HeaderValue::from_static("0-19") @@ -539,7 +503,7 @@ mod tests { #[test] fn range_assert_range_header() { - let builder = Builder::new(TABLE_URL, None).range(10, 20); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).range(10, 20); assert_eq!( builder.headers.get("Range").unwrap(), HeaderValue::from_static("10-20") @@ -548,7 +512,7 @@ mod tests { #[test] fn single_assert_accept_header() { - let builder = Builder::new(TABLE_URL, None).single(); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).single(); assert_eq!( builder.headers.get("Accept").unwrap(), HeaderValue::from_static("application/vnd.pgrst.object+json") @@ -557,7 +521,7 @@ mod tests { #[test] fn upsert_assert_prefer_header() { - let builder = Builder::new(TABLE_URL, None).upsert("ignored"); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).upsert("ignored"); assert_eq!( builder.headers.get("Prefer").unwrap(), HeaderValue::from_static("return=representation,resolution=merge-duplicates") @@ -566,20 +530,20 @@ mod tests { #[test] fn not_rpc_should_not_have_flag() { - let builder = Builder::new(TABLE_URL, None).select("ignored"); + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()).select("ignored"); assert_eq!(builder.is_rpc, false); } #[test] fn rpc_should_have_body_and_flag() { - let builder = Builder::new(RPC_URL, None).rpc("{\"a\": 1, \"b\": 2}"); + let builder = Builder::new(RPC_URL, None, HeaderMap::new()).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 builder = Builder::new(TABLE_URL, None) + let builder = Builder::new(TABLE_URL, None, HeaderMap::new()) .eq("username", "supabot") .neq("message", "hello world") .gte("channel_id", "1") diff --git a/src/lib.rs b/src/lib.rs index 0d28412..bb01584 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,16 +74,16 @@ //! [postgrest]: https://postgrest.org //! [readme]: https://github.com/supabase/postgrest-rs -extern crate reqwest; - mod builder; mod filter; pub use builder::Builder; +use reqwest::header::{HeaderMap, HeaderValue, IntoHeaderName}; pub struct Postgrest { url: String, schema: Option, + headers: HeaderMap, } impl Postgrest { @@ -103,6 +103,7 @@ impl Postgrest { Postgrest { url: url.into(), schema: None, + headers: HeaderMap::new(), } } @@ -128,6 +129,30 @@ impl Postgrest { self } + /// 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 @@ -143,7 +168,7 @@ impl Postgrest { T: AsRef, { let url = format!("{}/{}", self.url, table.as_ref()); - Builder::new(url, self.schema.clone()) + Builder::new(url, self.schema.clone(), self.headers.clone()) } /// Perform a stored procedure call. @@ -162,7 +187,7 @@ impl Postgrest { U: Into, { let url = format!("{}/rpc/{}", self.url, function.as_ref()); - Builder::new(url, self.schema.clone()).rpc(params) + Builder::new(url, self.schema.clone(), self.headers.clone()).rpc(params) } } @@ -184,4 +209,16 @@ mod tests { Some("private".to_string()) ); } + + #[test] + fn with_insert_header() { + assert_eq!( + Postgrest::new(REST_URL) + .insert_header("apikey", "super.secret.key") + .headers + .get("apikey") + .unwrap(), + "super.secret.key" + ); + } }