use dataflow_rs::engine::error::DataflowError;
use dataflow_rs::engine::task_context::TaskContext;
use futures::TryStreamExt;
use mongodb::bson::{self, Document};
use serde_json::Value;
use super::connector_helpers::{is_mongo, require_db_connector, resolve_value};
use crate::connector::{ConnectorConfig, DbConnectorConfig};
pub(super) fn require_mongo_connector<'a>(
config: &'a ConnectorConfig,
handler_name: &str,
connector_name: &str,
) -> Result<&'a DbConnectorConfig, DataflowError> {
let db_config = require_db_connector(config, connector_name)?;
if !is_mongo(&db_config.connection_string) {
return Err(DataflowError::Validation(format!(
"{handler_name} requires a MongoDB connector, but '{connector_name}' has a \
non-MongoDB connection string (expected a mongodb:// or mongodb+srv:// URL)"
)));
}
Ok(db_config)
}
pub(super) fn resolve_document(
input: &Value,
field: &str,
handler_name: &str,
ctx: &TaskContext<'_>,
) -> Result<Option<Document>, DataflowError> {
match input.get(field) {
None | Some(Value::Null) => Ok(None),
Some(raw) => {
let resolved = resolve_value(raw, ctx);
if !resolved.is_object() {
return Err(DataflowError::Validation(format!(
"{handler_name} '{field}' must resolve to an object"
)));
}
bson::to_document(&resolved).map(Some).map_err(|e| {
DataflowError::Validation(format!("{handler_name} '{field}' is not valid: {e}"))
})
}
}
}
pub(super) fn documents_from_values<'a>(
values: impl IntoIterator<Item = &'a Value>,
field: &str,
handler_name: &str,
) -> Result<Vec<Document>, DataflowError> {
values
.into_iter()
.enumerate()
.map(|(i, item)| {
if !item.is_object() {
return Err(DataflowError::Validation(format!(
"{handler_name} {field}[{i}] must be an object"
)));
}
bson::to_document(item).map_err(|e| {
DataflowError::Validation(format!("{handler_name} {field}[{i}] is not valid: {e}"))
})
})
.collect()
}
pub(super) fn resolve_document_array(
input: &Value,
field: &str,
handler_name: &str,
ctx: &TaskContext<'_>,
) -> Result<Option<Vec<Document>>, DataflowError> {
let raw = match input.get(field) {
None | Some(Value::Null) => return Ok(None),
Some(raw) => raw,
};
let resolved = resolve_value(raw, ctx);
let Value::Array(items) = resolved else {
return Err(DataflowError::Validation(format!(
"{handler_name} '{field}' must resolve to an array of objects"
)));
};
documents_from_values(items.iter(), field, handler_name).map(Some)
}
pub(super) fn require_document(
input: &Value,
field: &str,
handler_name: &str,
ctx: &TaskContext<'_>,
) -> Result<Document, DataflowError> {
resolve_document(input, field, handler_name, ctx)?.ok_or_else(|| {
DataflowError::Validation(format!("{handler_name} requires '{field}' field"))
})
}
pub(super) fn resolve_u64(
input: &Value,
field: &str,
handler_name: &str,
ctx: &TaskContext<'_>,
) -> Result<Option<u64>, DataflowError> {
match input.get(field) {
None | Some(Value::Null) => Ok(None),
Some(raw) => match resolve_value(raw, ctx) {
Value::Number(n) => n.as_u64().map(Some).ok_or_else(|| {
DataflowError::Validation(format!(
"{handler_name} '{field}' must be a non-negative integer"
))
}),
other => Err(DataflowError::Validation(format!(
"{handler_name} '{field}' must resolve to a non-negative integer, got {}",
super::connector_helpers::json_type_name(&other)
))),
},
}
}
pub(super) async fn drain_capped(
mut cursor: mongodb::Cursor<Document>,
cap: usize,
handler_name: &str,
) -> Result<Vec<Document>, String> {
let mut docs: Vec<Document> = Vec::new();
while let Some(doc) = cursor.try_next().await.map_err(|e| e.to_string())? {
if docs.len() >= cap {
return Err(format!(
"{handler_name} result exceeds query.max_limit ({cap} documents) — \
add a filter/limit or raise the cap"
));
}
docs.push(doc);
}
Ok(docs)
}
pub(super) fn docs_to_json(docs: &[Document]) -> Value {
Value::Array(
docs.iter()
.filter_map(|doc| serde_json::to_value(doc).ok())
.collect(),
)
}