use std::borrow::Cow;
use std::collections::BTreeMap;
use testcontainers_modules::testcontainers::core::{
ContainerPort, ContainerState, ExecCommand, WaitFor,
};
use testcontainers_modules::testcontainers::{Image, TestcontainersError};
const NAME: &str = "postgrest/postgrest";
const TAG: &str = "v12.2.3";
pub const POSTGREST_PORT: u16 = 3000;
#[derive(Debug, Clone)]
pub struct PostgREST {
env_vars: BTreeMap<String, String>,
tag: String,
}
impl PostgREST {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_env(envs: BTreeMap<&str, &str>) -> Self {
let mut instance = Self::default();
for (key, val) in envs {
instance.env_vars.insert(key.to_string(), val.to_string());
}
instance
}
pub fn with_postgres_connection(mut self, connection_string: &str) -> Self {
self.env_vars
.insert("PGRST_DB_URI".to_string(), connection_string.to_string());
self
}
pub fn with_db_schemas(mut self, schemas: &str) -> Self {
self.env_vars
.insert("PGRST_DB_SCHEMAS".to_string(), schemas.to_string());
self
}
pub fn with_db_anon_role(mut self, role: &str) -> Self {
self.env_vars
.insert("PGRST_DB_ANON_ROLE".to_string(), role.to_string());
self
}
pub fn with_jwt_secret(mut self, secret: impl Into<String>) -> Self {
self.env_vars
.insert("PGRST_JWT_SECRET".to_string(), secret.into());
self
}
pub fn with_jwt_role_claim_key(mut self, key: impl Into<String>) -> Self {
self.env_vars
.insert("PGRST_JWT_ROLE_CLAIM_KEY".to_string(), key.into());
self
}
pub fn with_openapi_mode(mut self, mode: impl Into<String>) -> Self {
self.env_vars
.insert("PGRST_OPENAPI_MODE".to_string(), mode.into());
self
}
pub fn with_max_rows(mut self, max_rows: u32) -> Self {
self.env_vars
.insert("PGRST_DB_MAX_ROWS".to_string(), max_rows.to_string());
self
}
pub fn with_pre_request(mut self, function_name: impl Into<String>) -> Self {
self.env_vars
.insert("PGRST_DB_PRE_REQUEST".to_string(), function_name.into());
self
}
pub fn with_log_level(mut self, level: impl Into<String>) -> Self {
self.env_vars
.insert("PGRST_LOG_LEVEL".to_string(), level.into());
self
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tag = tag.into();
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env_vars.insert(key.into(), value.into());
self
}
}
impl Default for PostgREST {
fn default() -> Self {
let mut env_vars = BTreeMap::new();
env_vars.insert("PGRST_DB_SCHEMAS".to_string(), "public".to_string());
env_vars.insert("PGRST_DB_ANON_ROLE".to_string(), "anon".to_string());
env_vars.insert("PGRST_SERVER_PORT".to_string(), POSTGREST_PORT.to_string());
env_vars.insert("PGRST_SERVER_HOST".to_string(), "0.0.0.0".to_string());
Self {
env_vars,
tag: TAG.to_string(),
}
}
}
impl Image for PostgREST {
fn name(&self) -> &str {
NAME
}
fn tag(&self) -> &str {
&self.tag
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stderr("Listening on port")]
}
fn expose_ports(&self) -> &[ContainerPort] {
&[ContainerPort::Tcp(POSTGREST_PORT)]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
#[allow(unused_variables)]
fn exec_after_start(
&self,
cs: ContainerState,
) -> Result<Vec<ExecCommand>, TestcontainersError> {
Ok(vec![])
}
}
#[cfg(test)]
#[cfg(feature = "postgrest")]
mod tests {
use super::*;
use testcontainers_modules::testcontainers::Image;
#[test]
fn test_default_configuration() {
let postgrest = PostgREST::default();
assert_eq!(
postgrest.env_vars.get("PGRST_DB_SCHEMAS"),
Some(&"public".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_DB_ANON_ROLE"),
Some(&"anon".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_SERVER_PORT"),
Some(&"3000".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_SERVER_HOST"),
Some(&"0.0.0.0".to_string())
);
}
#[test]
fn test_name_returns_correct_image() {
let postgrest = PostgREST::default();
assert_eq!(postgrest.name(), "postgrest/postgrest");
}
#[test]
fn test_tag_returns_correct_version() {
let postgrest = PostgREST::default();
assert_eq!(postgrest.tag(), "v12.2.3");
}
#[test]
fn test_postgrest_port_constant() {
assert_eq!(POSTGREST_PORT, 3000);
}
#[test]
fn test_with_postgres_connection() {
let postgrest =
PostgREST::default().with_postgres_connection("postgres://user:pass@localhost:5432/db");
assert_eq!(
postgrest.env_vars.get("PGRST_DB_URI"),
Some(&"postgres://user:pass@localhost:5432/db".to_string())
);
}
#[test]
fn test_with_db_schemas() {
let postgrest = PostgREST::default().with_db_schemas("public,api,private");
assert_eq!(
postgrest.env_vars.get("PGRST_DB_SCHEMAS"),
Some(&"public,api,private".to_string())
);
}
#[test]
fn test_with_db_anon_role() {
let postgrest = PostgREST::default().with_db_anon_role("web_anon");
assert_eq!(
postgrest.env_vars.get("PGRST_DB_ANON_ROLE"),
Some(&"web_anon".to_string())
);
}
#[test]
fn test_with_jwt_secret() {
let postgrest = PostgREST::default().with_jwt_secret("my-super-secret-jwt-key");
assert_eq!(
postgrest.env_vars.get("PGRST_JWT_SECRET"),
Some(&"my-super-secret-jwt-key".to_string())
);
}
#[test]
fn test_with_jwt_role_claim_key() {
let postgrest = PostgREST::default().with_jwt_role_claim_key(".app_metadata.role");
assert_eq!(
postgrest.env_vars.get("PGRST_JWT_ROLE_CLAIM_KEY"),
Some(&".app_metadata.role".to_string())
);
}
#[test]
fn test_with_openapi_mode() {
let postgrest = PostgREST::default().with_openapi_mode("follow-privileges");
assert_eq!(
postgrest.env_vars.get("PGRST_OPENAPI_MODE"),
Some(&"follow-privileges".to_string())
);
}
#[test]
fn test_with_max_rows() {
let postgrest = PostgREST::default().with_max_rows(1000);
assert_eq!(
postgrest.env_vars.get("PGRST_DB_MAX_ROWS"),
Some(&"1000".to_string())
);
}
#[test]
fn test_with_pre_request() {
let postgrest = PostgREST::default().with_pre_request("auth.check_request");
assert_eq!(
postgrest.env_vars.get("PGRST_DB_PRE_REQUEST"),
Some(&"auth.check_request".to_string())
);
}
#[test]
fn test_with_log_level() {
let postgrest = PostgREST::default().with_log_level("warn");
assert_eq!(
postgrest.env_vars.get("PGRST_LOG_LEVEL"),
Some(&"warn".to_string())
);
}
#[test]
fn test_with_tag_overrides_default() {
let postgrest = PostgREST::default().with_tag("v11.0.0");
assert_eq!(postgrest.tag(), "v11.0.0");
}
#[test]
fn test_with_env_adds_custom_variable() {
let postgrest = PostgREST::default()
.with_env("CUSTOM_VAR", "custom_value")
.with_env("ANOTHER_VAR", "another_value");
assert_eq!(
postgrest.env_vars.get("CUSTOM_VAR"),
Some(&"custom_value".to_string())
);
assert_eq!(
postgrest.env_vars.get("ANOTHER_VAR"),
Some(&"another_value".to_string())
);
}
#[test]
fn test_builder_method_chaining() {
let postgrest = PostgREST::default()
.with_postgres_connection("postgres://user:pass@localhost:5432/db")
.with_db_schemas("api")
.with_db_anon_role("web_anon")
.with_jwt_secret("secret")
.with_max_rows(500)
.with_log_level("info")
.with_tag("v11.0.0");
assert_eq!(
postgrest.env_vars.get("PGRST_DB_URI"),
Some(&"postgres://user:pass@localhost:5432/db".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_DB_SCHEMAS"),
Some(&"api".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_DB_ANON_ROLE"),
Some(&"web_anon".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_JWT_SECRET"),
Some(&"secret".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_DB_MAX_ROWS"),
Some(&"500".to_string())
);
assert_eq!(
postgrest.env_vars.get("PGRST_LOG_LEVEL"),
Some(&"info".to_string())
);
assert_eq!(postgrest.tag(), "v11.0.0");
}
#[test]
fn test_new_creates_default_instance() {
let postgrest = PostgREST::new();
assert_eq!(postgrest.name(), NAME);
assert_eq!(postgrest.tag(), TAG);
}
#[test]
fn test_expose_ports() {
let postgrest = PostgREST::default();
let ports = postgrest.expose_ports();
assert_eq!(ports.len(), 1);
assert_eq!(ports[0], ContainerPort::Tcp(3000));
}
#[test]
fn test_ready_conditions() {
let postgrest = PostgREST::default();
let conditions = postgrest.ready_conditions();
assert_eq!(conditions.len(), 1);
}
}