faucet_cli/commands/
preview.rs1use crate::cli::PreviewArgs;
9use crate::config::PipelineConfig;
10use crate::error::{CliError, CliResult};
11use crate::expand::{NodeRole, expand};
12use crate::registry::build_source;
13use crate::transforms::compile_transforms;
14use faucet_core::stage::{apply_stages, compile_stage};
15
16#[cfg(feature = "sink-stdout")]
17use faucet_core::{Pipeline, Sink};
18
19#[cfg(feature = "sink-stdout")]
21pub async fn run(args: PreviewArgs) -> CliResult<()> {
22 use faucet_sink_stdout::{StdoutFormat, StdoutSink, StdoutSinkConfig};
23 let cwd = std::env::current_dir()?;
24 let env_path =
25 crate::env_loader::resolve_env_file(args.env_file.as_deref(), args.no_env_file, &cwd)?;
26 crate::env_loader::load_env_file_if_present(env_path.as_deref())?;
27 let path = match args.config {
28 Some(p) => p,
29 None => crate::env_loader::discover_config_path(&cwd).ok_or(CliError::NoConfigOrFromEnv)?,
30 };
31 let cfg = PipelineConfig::from_path_async(&path, args.profile.as_deref()).await?;
32 let auth = crate::auth_catalog::build_auth_catalog(cfg.auth.as_ref())?;
33 let nodes = expand(&cfg)?;
34 let selection =
37 crate::select::RunSelection::from_args(&args.selection, cfg.selection.as_ref())?;
38 let nodes = crate::select::select_nodes(nodes, &selection, !cfg.matrix.is_empty())?;
39 let first_root = nodes
40 .iter()
41 .find(|n| matches!(n.role, NodeRole::Root))
42 .ok_or_else(|| CliError::ParseConfig {
43 path: std::path::PathBuf::from("(preview)"),
44 message: "no root rows in matrix to preview".to_owned(),
45 })?;
46 tracing::info!(row = %first_root.id, "previewing first root row");
47
48 let source = build_source(
49 &first_root.source.kind,
50 first_root.source.config.clone(),
51 &auth,
52 None,
53 )
54 .await?;
55 let stages = compile_transforms(&first_root.transforms)?;
56 let records = source.fetch_all().await?;
57 let records: Vec<_> = if stages.is_empty() {
58 records
59 } else {
60 let compiled = stages
61 .iter()
62 .map(compile_stage)
63 .collect::<Result<Vec<_>, _>>()?;
64 let mut out = Vec::with_capacity(records.len());
65 for r in records {
66 out.extend(apply_stages(r, &compiled)?);
67 }
68 out
69 };
70
71 let limited: Vec<_> = records.into_iter().take(args.limit).collect();
72 let sink = StdoutSink::new(
73 StdoutSinkConfig::new()
74 .format(StdoutFormat::JsonLines)
75 .flush_per_record(true),
76 );
77 sink.write_batch(&limited).await?;
78 sink.flush().await?;
79
80 let _ = std::marker::PhantomData::<Pipeline<'_, dyn faucet_core::Source, dyn Sink>>;
81 Ok(())
82}
83
84#[cfg(not(feature = "sink-stdout"))]
85pub async fn run(_args: PreviewArgs) -> CliResult<()> {
86 Err(CliError::UnknownConnector {
87 kind: "sink",
88 name: "stdout".into(),
89 available: "(preview requires faucet-cli to be built with the 'sink-stdout' feature)"
90 .into(),
91 })
92}