@@ -11,6 +11,7 @@ use sift_rs::{
1111 ping:: v1:: { PingRequest , ping_service_client:: PingServiceClient } ,
1212 runs:: v2:: Run ,
1313 wrappers:: {
14+ assets:: { AssetServiceWrapper , new_asset_service} ,
1415 ingestion_configs:: { IngestionConfigServiceWrapper , new_ingestion_config_service} ,
1516 runs:: { RunServiceWrapper , new_run_service} ,
1617 } ,
@@ -39,7 +40,8 @@ pub const DEFAULT_CHECKPOINT_INTERVAL: Duration = Duration::from_secs(60);
3940/// [tokio](https://docs.rs/tokio/latest/tokio/) asynchronous runtime is required, otherwise
4041/// attempts to call [SiftStreamBuilder::build] will panic.
4142pub struct SiftStreamBuilder < C > {
42- credentials : Credentials ,
43+ credentials : Option < Credentials > ,
44+ channel : Option < SiftChannel > ,
4345 recovery_strategy : Option < RecoveryStrategy > ,
4446 checkpoint_interval : Duration ,
4547 ingestion_config : Option < IngestionConfigForm > ,
@@ -171,7 +173,8 @@ where
171173 self
172174 }
173175
174- /// Disables TLS. Useful for testing.
176+ /// Disables TLS. Useful for testing. This is ignored if [SiftStreamBuilder::from_channel] is
177+ /// used to initialize the builder.
175178 pub fn disable_tls ( mut self ) -> SiftStreamBuilder < C > {
176179 self . enable_tls = false ;
177180 self
@@ -286,10 +289,26 @@ where
286289
287290/// Builds a [SiftStream] specifically for ingestion-config based streaming.
288291impl SiftStreamBuilder < IngestionConfigMode > {
289- /// Initializes a new builder for ingestion-config-based streaming.
292+ /// Initializes a new builder for ingestion-config-based streaming from [Credentials] .
290293 pub fn new ( credentials : Credentials ) -> SiftStreamBuilder < IngestionConfigMode > {
291294 SiftStreamBuilder {
292- credentials,
295+ credentials : Some ( credentials) ,
296+ channel : None ,
297+ enable_tls : true ,
298+ ingestion_config : None ,
299+ run : None ,
300+ run_id : None ,
301+ kind : PhantomData ,
302+ checkpoint_interval : DEFAULT_CHECKPOINT_INTERVAL ,
303+ recovery_strategy : None ,
304+ }
305+ }
306+
307+ /// Initializes a new builder for ingestion-config-based streaming from a [SiftChannel].
308+ pub fn from_channel ( channel : SiftChannel ) -> SiftStreamBuilder < IngestionConfigMode > {
309+ SiftStreamBuilder {
310+ credentials : None ,
311+ channel : Some ( channel) ,
293312 enable_tls : true ,
294313 ingestion_config : None ,
295314 run : None ,
@@ -304,26 +323,37 @@ impl SiftStreamBuilder<IngestionConfigMode> {
304323 /// streaming.
305324 pub async fn build ( self ) -> Result < SiftStream < IngestionConfigMode > > {
306325 let SiftStreamBuilder {
307- credentials,
308326 checkpoint_interval,
327+ channel : grpc_channel,
328+ credentials,
329+ enable_tls,
309330 ingestion_config,
331+ recovery_strategy,
310332 run,
311333 run_id,
312- recovery_strategy,
313- enable_tls,
314334 ..
315335 } = self ;
316336
317337 let Some ( ingestion_config) = ingestion_config else {
318338 return Err ( Error :: new_arg_error ( "ingestion_config is required" ) ) ;
319339 } ;
320340
321- let mut sift_channel_builder = SiftChannelBuilder :: new ( credentials) ;
341+ let channel = match grpc_channel {
342+ Some ( ch) => ch,
343+ None if credentials. is_some ( ) => {
344+ let mut sift_channel_builder = SiftChannelBuilder :: new ( credentials. unwrap ( ) ) ;
322345
323- if enable_tls {
324- sift_channel_builder = sift_channel_builder. use_tls ( true ) ;
325- }
326- let channel = sift_channel_builder. build ( ) ?;
346+ if enable_tls {
347+ sift_channel_builder = sift_channel_builder. use_tls ( true ) ;
348+ }
349+ sift_channel_builder. build ( ) ?
350+ }
351+ None => {
352+ return Err ( Error :: new_arg_error (
353+ "either credentials or a gRPC channel must be provided" ,
354+ ) ) ;
355+ }
356+ } ;
327357
328358 // Since the gRPC connection is lazy, we'll connect right away and ensure the connection is
329359 // valid.
@@ -420,7 +450,8 @@ impl SiftStreamBuilder<IngestionConfigMode> {
420450 #[ cfg( feature = "tracing" ) ]
421451 tracing:: info_span!( "load_ingestion_config" ) ;
422452
423- let mut ingestion_config_service = new_ingestion_config_service ( grpc_channel) ;
453+ let mut ingestion_config_service = new_ingestion_config_service ( grpc_channel. clone ( ) ) ;
454+ let mut asset_service = new_asset_service ( grpc_channel) ;
424455
425456 let IngestionConfigForm {
426457 asset_name,
@@ -437,24 +468,31 @@ impl SiftStreamBuilder<IngestionConfigMode> {
437468 . try_create_ingestion_config ( & asset_name, & client_key, & flows)
438469 . await ?;
439470
440- let flows = ingestion_config_service
441- . try_filter_flows ( & ingestion_config. ingestion_config_id , "" )
442- . await ?;
471+ let new_flows = {
472+ if flows. is_empty ( ) {
473+ Vec :: new ( )
474+ } else {
475+ ingestion_config_service
476+ . try_filter_flows ( & ingestion_config. ingestion_config_id , "" )
477+ . await ?
478+ }
479+ } ;
443480
444481 #[ cfg( feature = "tracing" ) ]
445482 {
446- let flow_names = flows
447- . iter ( )
448- . map ( |f| f. name . as_str ( ) )
449- . collect :: < Vec < & str > > ( )
450- . join ( "," ) ;
451- tracing:: info!(
452- ingestion_config_id = ingestion_config. ingestion_config_id,
453- flow_names = flow_names,
454- "created new ingestion config"
455- ) ;
483+ if !new_flows. is_empty ( ) {
484+ let flow_names = new_flows
485+ . iter ( )
486+ . map ( |f| f. name . as_str ( ) )
487+ . collect :: < Vec < & str > > ( )
488+ . join ( "," ) ;
489+ tracing:: info!(
490+ ingestion_config_id = ingestion_config. ingestion_config_id,
491+ flow_names = flow_names,
492+ "created new ingestion config"
493+ ) ;
494+ }
456495 }
457-
458496 Ok ( ( ingestion_config, flows) )
459497 }
460498 Err ( err) => Err ( err) ,
@@ -466,13 +504,32 @@ impl SiftStreamBuilder<IngestionConfigMode> {
466504 "an existing ingestion config was found with the provided client-key"
467505 ) ;
468506
507+ let asset = asset_service
508+ . try_get_asset_by_id ( & ingestion_config. asset_id )
509+ . await
510+ . context ( "failed to retrieve asset specified by ingestion config" ) ?;
511+
512+ if asset. name != asset_name {
513+ return Err ( Error :: new_msg (
514+ ErrorKind :: IncompatibleIngestionConfigChange ,
515+ format ! (
516+ "local ingestion config references asset '{asset_name}' but this existing config in Sift refers to asset '{}'" ,
517+ asset. name
518+ ) ,
519+ ) ) ;
520+ }
521+
469522 let flow_names = flows
470523 . iter ( )
471524 . map ( |f| format ! ( "'{}'" , f. name) )
472525 . collect :: < Vec < String > > ( )
473526 . join ( "," ) ;
474527
475- let filter = format ! ( "flow_name in [{flow_names}]" ) ;
528+ let filter = flow_names
529+ . is_empty ( )
530+ . then ( String :: new)
531+ . unwrap_or_else ( || format ! ( "flow_name in [{flow_names}]" ) ) ;
532+
476533 let existing_flows = ingestion_config_service
477534 . try_filter_flows ( & ingestion_config. ingestion_config_id , & filter)
478535 . await ?;
@@ -534,3 +591,15 @@ impl SiftStreamBuilder<IngestionConfigMode> {
534591 }
535592 }
536593}
594+
595+ impl From < Credentials > for SiftStreamBuilder < IngestionConfigMode > {
596+ fn from ( value : Credentials ) -> Self {
597+ Self :: new ( value)
598+ }
599+ }
600+
601+ impl From < SiftChannel > for SiftStreamBuilder < IngestionConfigMode > {
602+ fn from ( value : SiftChannel ) -> Self {
603+ Self :: from_channel ( value)
604+ }
605+ }
0 commit comments