use std::sync::Arc;
use serde_json::Value;
use crate::actor::Actor;
use crate::actor_policy::{ActorJsonPolicy, ActorTrust};
use crate::error::{Error, Result};
use crate::ports::actor::{ActorFactory, JsonActorFactory};
use crate::ports::endpoints::DatabaseEndpointResolver;
use crate::ports::secrets::SecretProvider;
use crate::router::DatabaseRouter;
use crate::runtime::{Valence, ValenceBuilder};
use valence_telemetry::TelemetrySink;
pub trait ValenceFactory: Send + Sync + 'static {
fn build(&self, actor_json: &Value) -> Result<Valence>;
}
#[derive(Clone)]
pub struct RouterValenceFactoryConfig {
pub default_backend_key: String,
pub telemetry_sink: Option<Arc<dyn TelemetrySink>>,
pub secret_provider: Option<Arc<dyn SecretProvider>>,
pub actor_factory: Option<Arc<dyn ActorFactory>>,
pub endpoint_resolver: Option<Arc<dyn DatabaseEndpointResolver>>,
pub actor_json_policy: Option<Arc<dyn ActorJsonPolicy>>,
pub actor_trust: ActorTrust,
}
impl RouterValenceFactoryConfig {
#[must_use]
pub fn new(default_backend_key: impl Into<String>) -> Self {
Self {
default_backend_key: default_backend_key.into(),
telemetry_sink: None,
secret_provider: None,
actor_factory: None,
endpoint_resolver: None,
actor_json_policy: None,
actor_trust: ActorTrust::External,
}
}
#[must_use]
pub fn actor_json_policy(mut self, policy: impl ActorJsonPolicy + 'static) -> Self {
self.actor_json_policy = Some(Arc::new(policy));
self
}
}
#[derive(Clone)]
pub struct RouterValenceFactory {
router: Arc<DatabaseRouter>,
config: RouterValenceFactoryConfig,
}
impl RouterValenceFactory {
#[must_use]
pub fn new(router: Arc<DatabaseRouter>, config: RouterValenceFactoryConfig) -> Self {
Self { router, config }
}
pub fn arc(
router: Arc<DatabaseRouter>,
config: RouterValenceFactoryConfig,
) -> Arc<dyn ValenceFactory> {
Arc::new(Self::new(router, config))
}
}
impl ValenceFactory for RouterValenceFactory {
fn build(&self, actor_json: &Value) -> Result<Valence> {
if let Some(policy) = &self.config.actor_json_policy {
policy.validate(self.config.actor_trust, actor_json)?;
}
let actor_factory = self
.config
.actor_factory
.clone()
.unwrap_or_else(|| Arc::new(JsonActorFactory));
let _actor_ctx = actor_factory.build(actor_json)?;
let actor: Actor = serde_json::from_value(actor_json.clone()).map_err(|e| {
Error::Validation(format!("actor_json is not a valid valence::Actor: {e}"))
})?;
let mut builder = ValenceBuilder::new()
.database_router(Arc::clone(&self.router))
.default_backend_key(self.config.default_backend_key.clone())
.actor_factory(actor_factory)
.with_actor(actor);
if let Some(sink) = &self.config.telemetry_sink {
builder = builder.telemetry_sink(Arc::clone(sink));
}
if let Some(secrets) = &self.config.secret_provider {
builder = builder.secret_provider(Arc::clone(secrets));
}
if let Some(endpoints) = &self.config.endpoint_resolver {
builder = builder.endpoint_resolver(Arc::clone(endpoints));
}
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actor::Actor;
use crate::backend::DatabaseBackend;
use async_trait::async_trait;
use std::sync::Arc;
#[derive(Debug)]
struct MockBackend;
#[async_trait]
impl DatabaseBackend for MockBackend {
fn engine_id(&self) -> &'static str {
"mem"
}
fn capabilities(&self) -> crate::backend::BackendCapabilities {
crate::backend::BackendCapabilities::mem()
}
async fn execute_compiled_query(
&self,
_compiled: &crate::compiled_query::CompiledQuery,
) -> crate::error::Result<Vec<serde_json::Value>> {
Ok(vec![])
}
async fn get_record(
&self,
_table: &str,
_id: &str,
) -> crate::error::Result<Option<serde_json::Value>> {
Ok(None)
}
async fn create_record(
&self,
_table: &str,
_content: serde_json::Value,
) -> crate::error::Result<serde_json::Value> {
Ok(serde_json::json!({}))
}
async fn update_record(
&self,
_table: &str,
_id: &str,
_content: serde_json::Value,
) -> crate::error::Result<serde_json::Value> {
Ok(serde_json::json!({}))
}
async fn upsert_record(
&self,
_table: &str,
_id: &str,
_content: serde_json::Value,
) -> crate::error::Result<serde_json::Value> {
Ok(serde_json::json!({}))
}
async fn delete_record(&self, _table: &str, _id: &str) -> crate::error::Result<()> {
Ok(())
}
async fn relate_edge(
&self,
_from: &crate::record_id::RecordId,
_edge_table: &str,
_to: &crate::record_id::RecordId,
) -> crate::error::Result<()> {
Ok(())
}
async fn unrelate_edge(
&self,
_from: &crate::record_id::RecordId,
_edge_table: &str,
_to: &crate::record_id::RecordId,
) -> crate::error::Result<()> {
Ok(())
}
async fn get_edge_targets(
&self,
_from: &crate::record_id::RecordId,
_edge_table: &str,
) -> crate::error::Result<Vec<crate::record_id::RecordId>> {
Ok(vec![])
}
}
fn factory() -> RouterValenceFactory {
let valence = Valence::builder()
.add_backend("default", Arc::new(MockBackend))
.build()
.expect("build");
let router = Arc::clone(valence.database_router());
RouterValenceFactory::new(router, RouterValenceFactoryConfig::new("default"))
}
#[test]
fn build_binds_user_actor_from_json() {
let f = factory();
let actor_json = serde_json::to_value(Actor::User {
user_id: "u1".into(),
})
.expect("json");
let v = f.build(&actor_json).expect("build");
assert_eq!(v.actor().user_id(), Some("u1"));
}
#[test]
fn build_rejects_invalid_actor_json() {
let f = factory();
let err = f
.build(&serde_json::json!({"kind": "not-an-actor"}))
.expect_err("invalid");
assert!(err.to_string().contains("actor_json"));
}
#[test]
fn build_rejects_external_system_when_policy_installed() {
use crate::actor_policy::RejectExternalSystemActor;
let valence = Valence::builder()
.add_backend("default", Arc::new(MockBackend))
.build()
.expect("build");
let router = Arc::clone(valence.database_router());
let config =
RouterValenceFactoryConfig::new("default").actor_json_policy(RejectExternalSystemActor);
let f = RouterValenceFactory::new(router, config);
let system = serde_json::to_value(Actor::System {
operation: "x".into(),
})
.expect("json");
let err = f.build(&system).expect_err("system");
assert!(err.to_string().contains("System"));
}
}