Skip to main content

vector/config/
sink_validated.rs

1//! Validated sink infrastructure for the sinks-only lifecycle architecture.
2//!
3//! This module implements a split between the serde/typetag-facing `SinkConfig` role
4//! and the topology-facing runtime role. It introduces:
5//!
6//! - `ValidatedSink`: a generic, implementor-facing trait. A migrated sink implements
7//!   this and works entirely with its own concrete validated type `T` — it returns `T`
8//!   from `validate` and receives `&T` back in `build`. No `Any` in sight.
9//! - `DynValidatedSink`: the object-safe boundary the framework actually crosses.
10//!   A blanket impl derives it from any `ValidatedSink`, performing the `Box<dyn Any>`
11//!   erasure and downcast automatically.
12//!
13//! The erased validated state is stored on `SinkOuter` (see `SinkOuter::validated`) and
14//! consumed at build time by `SinkOuter::build`, which dispatches through
15//! `DynValidatedSink::build_dyn`.
16//!
17//! `ValidatedSink::validate` is the same phase as the RFC's
18//! `SinkConfig::validate_structure` (pure structural validation owned by config
19//! compilation), but it retains the validated state so `build` does not
20//! redo it.
21//!
22//! Design goals:
23//! - Validation returns retained values passed to build
24//! - Validation is pure: no filesystem/network/credentials/spawn/await
25//! - Build may do environment-dependent construction but must not redo pure validation
26//! - Preserve raw config serialization, reload diffing, and metadata access
27//! - Sink implementors never touch `Any`; the framework owns the erasure
28
29use std::any::Any;
30
31use async_trait::async_trait;
32use vector_lib::sink::VectorSink;
33
34use super::SinkContext;
35use crate::sinks::Healthcheck;
36
37/// Generic validated-sink trait, implemented by migrated sinks.
38///
39/// The implementor works entirely with their concrete validated type `Self::Validated`:
40///
41/// ```ignore
42/// #[async_trait]
43/// impl ValidatedSink for MySinkConfig {
44///     type Validated = ValidatedMySink;
45///
46///     fn validate(&self) -> crate::Result<Self::Validated> {
47///         ValidatedMySink::from_config(self)
48///     }
49///
50///     async fn build(
51///         &self,
52///         validated: &Self::Validated,
53///         cx: SinkContext,
54///     ) -> crate::Result<(VectorSink, Healthcheck)> {
55///         validated.build(cx).await
56///     }
57/// }
58/// ```
59///
60/// The framework automatically erases `Self::Validated` to `Box<dyn Any>` and restores
61/// it at build time via `DynValidatedSink`, so no `Any` appears in implementor code.
62#[async_trait]
63pub trait ValidatedSink {
64    /// The concrete validated state produced by `validate` and consumed by `build`.
65    type Validated: Send + Sync + 'static;
66
67    /// Performs pure structural validation, returning the validated state.
68    ///
69    /// This is the same phase as the RFC's `SinkConfig::validate_structure`, but it
70    /// retains the validated state so `build` does not redo it.
71    ///
72    /// # Purity Guarantees
73    ///
74    /// This method must be pure: no filesystem access, no network operations, no
75    /// credential resolution, no spawning, and no async/await. All such environment-
76    /// dependent operations belong in `build`.
77    fn validate(&self) -> crate::Result<Self::Validated>;
78
79    /// Builds the sink from the validated state, without redoing pure validation.
80    ///
81    /// May perform environment-dependent construction (HTTP clients, schema fetching, etc.).
82    async fn build(
83        &self,
84        validated: &Self::Validated,
85        cx: SinkContext,
86    ) -> crate::Result<(VectorSink, Healthcheck)>;
87}
88
89/// Object-safe `dyn` boundary used by the framework.
90///
91/// This is what actually crosses the `Box<dyn SinkConfig>` boundary. It is derived
92/// automatically from any `ValidatedSink` by the blanket impl below, which owns the
93/// `Box<dyn Any>` erasure and the downcast back to the concrete validated type.
94#[async_trait]
95pub trait DynValidatedSink {
96    /// Erases the validated state into a `Box<dyn Any>`.
97    fn validate_dyn(&self) -> crate::Result<Box<dyn Any + Send + Sync>>;
98
99    /// Restores the validated state from `&dyn Any` and builds the sink.
100    async fn build_dyn(
101        &self,
102        validated: &(dyn Any + Send + Sync),
103        cx: SinkContext,
104    ) -> crate::Result<(VectorSink, Healthcheck)>;
105}
106
107#[async_trait]
108impl<T> DynValidatedSink for T
109where
110    T: ValidatedSink + Send + Sync + 'static,
111{
112    fn validate_dyn(&self) -> crate::Result<Box<dyn Any + Send + Sync>> {
113        Ok(Box::new(self.validate()?))
114    }
115
116    async fn build_dyn(
117        &self,
118        validated: &(dyn Any + Send + Sync),
119        cx: SinkContext,
120    ) -> crate::Result<(VectorSink, Healthcheck)> {
121        let validated = validated
122            .downcast_ref::<T::Validated>()
123            .expect("validated state type mismatch");
124        self.build(validated, cx).await
125    }
126}