Skip to content

Commit 729538e

Browse files
authored
feat: Add the FDv2 data system configuration API (#193)
## Summary Adds the public API for configuring an FDv2 data system and selecting it over the FDv1 data source. - `ConfigBuilder::data_system` takes a `DataSystemBuilder` and, when set, supersedes the FDv1 `data_source`. It is ignored in offline or daemon mode. - `DataSystemBuilder::default()` provides the recommended setup, while `custom()` lets the caller assemble the sources and FDv1 fallback explicitly and tune the fallback and recovery timeouts. - `FDv2StreamingBuilder` and `FDv2PollingBuilder` configure individual sources, each with a `base_url` override. The builders are public, while the factory and per-source configuration traits behind them stay internal. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Adds an optional **FDv2 data system** path on `ConfigBuilder::data_system`, wired through `Client` startup so a configured `DataSystemBuilder` **replaces** the legacy FDv1 `data_source` (with warnings and a null data source when both are set). Offline and daemon mode still drop custom data-system config like they do for custom data sources. > > Introduces **`data_system_builders`** with `DataSystemBuilder` (`custom()` vs `default()` recommended stack: poll initializer, stream + poll synchronizers, FDv1 polling fallback), plus `FDv2StreamingBuilder` and `FDv2PollingBuilder` for URL/transport/reconnect tuning. Factory wiring builds `FDv2DataSystem`, optionally wrapping an FDv1 fallback via `FDv1AdapterFactory`. > > Exposes an experimental **`data_sources`** module and widens visibility on FDv2 traits/types (`Initializer`/`Synchronizer`, `ChangeSet`, errors, headers) so custom sources can be implemented; adds polling/streaming **factory** types to instantiate sources per orchestrator run. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 14c20fc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6996466 commit 729538e

14 files changed

Lines changed: 767 additions & 40 deletions

File tree

launchdarkly-server-sdk/src/client.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use tokio::sync::{broadcast, Semaphore};
1414
use super::config::Config;
1515
use super::data_source_builders::BuildError as DataSourceError;
1616
use super::data_system::{DataSystem, FDv1DataSystem};
17+
use super::data_system_builders::BuildError as DataSystemError;
1718
use super::evaluation::{FlagDetail, FlagDetailConfig};
1819
use super::stores::store::DataStore;
1920
use super::stores::store_builders::BuildError as DataStoreError;
@@ -65,6 +66,12 @@ impl From<DataSourceError> for BuildError {
6566
}
6667
}
6768

69+
impl From<DataSystemError> for BuildError {
70+
fn from(error: DataSystemError) -> Self {
71+
Self::InvalidConfig(error.to_string())
72+
}
73+
}
74+
6875
impl From<DataStoreError> for BuildError {
6976
fn from(error: DataStoreError) -> Self {
7077
Self::InvalidConfig(error.to_string())
@@ -184,13 +191,24 @@ impl Client {
184191
let event_processor =
185192
event_processor_builder.build(&endpoints, config.sdk_key(), tags.clone())?;
186193

187-
let mut data_source_builder = config.data_source_builder().to_owned();
188-
data_source_builder.set_instance_id(instance_id);
189-
let data_source = data_source_builder.build(&endpoints, config.sdk_key(), tags.clone())?;
190-
let data_system: Arc<dyn DataSystem> = Arc::new(FDv1DataSystem::new(
191-
data_source,
192-
config.data_store_builder(),
193-
)?);
194+
let data_system: Arc<dyn DataSystem> = match config.data_system_builder() {
195+
Some(data_system_builder) => data_system_builder.build(
196+
&endpoints,
197+
config.sdk_key(),
198+
tags.as_deref(),
199+
&instance_id,
200+
)?,
201+
None => {
202+
let mut data_source_builder = config.data_source_builder().to_owned();
203+
data_source_builder.set_instance_id(instance_id);
204+
let data_source =
205+
data_source_builder.build(&endpoints, config.sdk_key(), tags.clone())?;
206+
Arc::new(FDv1DataSystem::new(
207+
data_source,
208+
config.data_store_builder(),
209+
)?)
210+
}
211+
};
194212
let data_store = data_system.store();
195213

196214
let events_default = EventsScope {

launchdarkly-server-sdk/src/config.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use thiserror::Error;
22

33
use crate::data_source_builders::{DataSourceFactory, NullDataSourceBuilder};
4+
use crate::data_system_builders::{DataSystemBuilder, DataSystemFactory};
45

56
#[cfg(any(
67
feature = "hyper-rustls-native-roots",
@@ -136,6 +137,7 @@ pub struct Config {
136137
service_endpoints_builder: ServiceEndpointsBuilder,
137138
data_store_builder: Box<dyn DataStoreFactory>,
138139
data_source_builder: Box<dyn DataSourceFactory>,
140+
data_system_builder: Option<Box<dyn DataSystemFactory>>,
139141
event_processor_builder: Box<dyn EventProcessorFactory>,
140142
application_tag: Option<String>,
141143
instance_id: String,
@@ -164,6 +166,11 @@ impl Config {
164166
self.data_source_builder.borrow()
165167
}
166168

169+
/// Returns the DataSystemFactory, if an FDv2 data system was configured.
170+
pub(crate) fn data_system_builder(&self) -> Option<&dyn DataSystemFactory> {
171+
self.data_system_builder.as_deref()
172+
}
173+
167174
/// Returns the EventProcessorFactory
168175
pub fn event_processor_builder(&self) -> &dyn EventProcessorFactory {
169176
self.event_processor_builder.borrow()
@@ -212,6 +219,7 @@ pub struct ConfigBuilder {
212219
service_endpoints_builder: Option<ServiceEndpointsBuilder>,
213220
data_store_builder: Option<Box<dyn DataStoreFactory>>,
214221
data_source_builder: Option<Box<dyn DataSourceFactory>>,
222+
data_system_builder: Option<Box<dyn DataSystemFactory>>,
215223
event_processor_builder: Option<Box<dyn EventProcessorFactory>>,
216224
application_info: Option<ApplicationInfo>,
217225
offline: bool,
@@ -226,6 +234,7 @@ impl ConfigBuilder {
226234
service_endpoints_builder: None,
227235
data_store_builder: None,
228236
data_source_builder: None,
237+
data_system_builder: None,
229238
event_processor_builder: None,
230239
offline: false,
231240
daemon_mode: false,
@@ -258,6 +267,16 @@ impl ConfigBuilder {
258267
self
259268
}
260269

270+
/// Set the data system to use for this client.
271+
///
272+
/// When set, the data system supersedes the [data_source](ConfigBuilder::data_source).
273+
/// If offline mode is enabled, it will be ignored.
274+
pub fn data_system(mut self, builder: &DataSystemBuilder) -> Self {
275+
let factory: Box<dyn DataSystemFactory> = Box::new(builder.clone());
276+
self.data_system_builder = Some(factory);
277+
self
278+
}
279+
261280
/// Set the event processor to use for this client.
262281
/// For usage see [EventProcessorBuilder](crate::EventProcessorBuilder).
263282
///
@@ -307,8 +326,27 @@ impl ConfigBuilder {
307326
Some(_data_store_builder) => self.data_store_builder.unwrap(),
308327
};
309328

329+
// The data system is optional; when set it supersedes the data source.
330+
// Like the data source, it is ignored in offline or daemon mode.
331+
let data_system_builder = match self.data_system_builder {
332+
Some(_) if self.offline => {
333+
warn!("Custom data system builders will be ignored when in offline mode");
334+
None
335+
}
336+
Some(_) if self.daemon_mode => {
337+
warn!("Custom data system builders will be ignored when in daemon mode");
338+
None
339+
}
340+
other => other,
341+
};
342+
310343
let data_source_builder_result: Result<Box<dyn DataSourceFactory>, BuildError> =
311344
match self.data_source_builder {
345+
None if data_system_builder.is_some() => Ok(Box::new(NullDataSourceBuilder::new())),
346+
Some(_) if data_system_builder.is_some() => {
347+
warn!("Custom data source builders will be ignored when a data system is configured");
348+
Ok(Box::new(NullDataSourceBuilder::new()))
349+
}
312350
None if self.offline => Ok(Box::new(NullDataSourceBuilder::new())),
313351
Some(_) if self.offline => {
314352
warn!("Custom data source builders will be ignored when in offline mode");
@@ -401,6 +439,7 @@ impl ConfigBuilder {
401439
service_endpoints_builder,
402440
data_store_builder,
403441
data_source_builder,
442+
data_system_builder,
404443
event_processor_builder,
405444
application_tag,
406445
instance_id,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Types for implementing custom FDv2 data sources.
2+
//!
3+
//! This module is experimental and not subject to semantic versioning. Its API
4+
//! may change in any release.
5+
6+
pub use crate::data_system_builders::{
7+
DataSourceBuildContext, FDv2InitializerConfig, FDv2SynchronizerConfig,
8+
};
9+
pub use crate::fdv2::data_system::{InitializerFactory, SynchronizerFactory};
10+
pub use crate::fdv2::model::{ChangeSetKind, Selector};
11+
pub use crate::fdv2::request_headers::RequestHeaders;
12+
pub use crate::fdv2::source::{
13+
ErrorInfo, ErrorKind, FDv1FallbackDirective, FDv2SourceEvent, FDv2SourceResult, Initializer,
14+
Synchronizer,
15+
};
16+
pub use crate::stores::change_set::{ChangeSet, ItemChange};

0 commit comments

Comments
 (0)