use serde_json::{Map, Value};
use super::error::ProvisioningError;
pub fn json_object_insert_if_missing(
input: Option<Value>,
key: &str,
value: Value,
) -> Result<Value, ProvisioningError> {
let mut object: Map<String, Value> = if let Some(input) = input {
match input {
Value::Object(map) => map,
_ => {
return Err(ProvisioningError::InvalidInput(format!(
"input must be a JSON object for key '{}'",
key
)));
}
}
} else {
Map::new()
};
object.entry(key.to_string()).or_insert(value);
Ok(Value::Object(object))
}
pub(crate) fn extract_connection_uri(value: &Value) -> Option<String> {
let preferred_pointers = [
"/uri",
"/connection_uri",
"/data/uri",
"/data/connection_uri",
"/data/variables/DATABASE_URL",
"/data/variables/DATABASE_PRIVATE_URL",
"/data/variables/DATABASE_PUBLIC_URL",
"/data/variables/POSTGRES_URL",
"/data/variables/PGDATABASE_URL",
];
for pointer in preferred_pointers {
if let Some(found) = value.pointer(pointer).and_then(Value::as_str)
&& is_postgres_uri(found)
{
return Some(found.to_string());
}
}
extract_from_variables_payload(value).or_else(|| recursive_find_postgres_uri(value))
}
pub(crate) fn extract_render_service_id(value: &Value) -> Option<String> {
let pointers = [
"/id",
"/service/id",
"/postgres/id",
"/database/id",
"/data/id",
"/data/service/id",
];
for pointer in pointers {
if let Some(id) = value.pointer(pointer).and_then(Value::as_str)
&& !id.trim().is_empty()
{
return Some(id.to_string());
}
}
None
}
pub(crate) fn extract_render_connection_uri(value: &Value) -> Option<String> {
let pointers = [
"/connectionString",
"/databaseUrl",
"/externalConnectionString",
"/internalConnectionString",
"/info/connectionString",
"/info/databaseUrl",
"/connectionInfo/connectionString",
"/connectionInfo/databaseUrl",
"/postgres/connectionString",
"/database/connectionString",
"/data/connectionString",
"/data/databaseUrl",
];
for pointer in pointers {
if let Some(candidate) = value.pointer(pointer).and_then(Value::as_str)
&& is_postgres_uri(candidate)
{
return Some(candidate.to_string());
}
}
None
}
fn extract_from_variables_payload(value: &Value) -> Option<String> {
let variables = value
.pointer("/data/variables")
.or_else(|| value.get("variables"))?;
match variables {
Value::Object(map) => {
let preferred_keys = [
"DATABASE_URL",
"DATABASE_PRIVATE_URL",
"DATABASE_PUBLIC_URL",
"POSTGRES_URL",
"PGDATABASE_URL",
"DATABASE_CONNECTION_URL",
];
for key in preferred_keys {
if let Some(candidate) = map.get(key).and_then(Value::as_str)
&& is_postgres_uri(candidate)
{
return Some(candidate.to_string());
}
}
for candidate in map.values().filter_map(Value::as_str) {
if is_postgres_uri(candidate) {
return Some(candidate.to_string());
}
}
None
}
Value::Array(items) => {
for item in items {
let name = item.get("name").and_then(Value::as_str).unwrap_or_default();
let candidate = item
.get("value")
.or_else(|| item.get("rawValue"))
.and_then(Value::as_str);
if let Some(candidate) = candidate
&& (name.eq_ignore_ascii_case("DATABASE_URL") || is_postgres_uri(candidate))
{
return Some(candidate.to_string());
}
}
None
}
Value::String(text) => {
if is_postgres_uri(text) {
Some(text.to_string())
} else {
None
}
}
_ => None,
}
}
fn recursive_find_postgres_uri(value: &Value) -> Option<String> {
match value {
Value::String(text) if is_postgres_uri(text) => Some(text.to_string()),
Value::Array(items) => items.iter().find_map(recursive_find_postgres_uri),
Value::Object(map) => map.values().find_map(recursive_find_postgres_uri),
_ => None,
}
}
fn is_postgres_uri(value: &str) -> bool {
let lower = value.to_lowercase();
lower.starts_with("postgres://") || lower.starts_with("postgresql://")
}
#[cfg(test)]
mod tests {
use super::{extract_connection_uri, extract_render_service_id};
use serde_json::json;
#[test]
fn extract_render_service_id_supports_common_shapes() {
let payload = json!({ "service": { "id": "srv-123" } });
assert_eq!(
extract_render_service_id(&payload),
Some("srv-123".to_string())
);
}
#[test]
fn extract_connection_uri_supports_render_connection_payload_shape() {
let payload = json!({
"connectionInfo": {
"connectionString": "postgres://user:pass@host:5432/db"
}
});
assert_eq!(
extract_connection_uri(&payload),
Some("postgres://user:pass@host:5432/db".to_string())
);
}
}