Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
499 changes: 386 additions & 113 deletions python/docs/examples/ingestion.ipynb

Large diffs are not rendered by default.

57 changes: 53 additions & 4 deletions rust/crates/sift_connect/src/grpc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,62 @@ use toml::{Table, Value};
/// The expected name of the config file.
pub const SIFT_CONFIG_NAME: &str = "sift.toml";

/// Specifies source of credentials. If `Profile` is used, then the provided string will be used to
/// query the corresponding table from [`SIFT_CONFIG_NAME`] located at
/// [these locations](https://docs.rs/dirs/6.0.0/dirs/fn.config_local_dir.html)
/// depending on your operating system. If `None` is provided, then the top-level table is used.
/// Specifies the source of credentials for connecting to Sift.
///
/// Credentials can be provided either directly via `Config` or loaded from a
/// configuration file using `Profile`.
///
/// # Profile-based Credentials
///
/// If `Profile` is used, the provided string will be used to query the corresponding
/// table from [`SIFT_CONFIG_NAME`] located at [these locations](https://docs.rs/dirs/6.0.0/dirs/fn.config_local_dir.html)
/// depending on your operating system. If `None` is provided, then the top-level
/// table is used.
///
/// Example `sift.toml` file:
///
/// ```toml
/// uri = "https://api.siftstack.com"
/// apikey = "default-api-key"
///
/// [production]
/// uri = "https://api.siftstack.com"
/// apikey = "production-api-key"
/// ```
///
/// # Direct Credentials
///
/// The `Config` variant allows you to provide credentials directly without
/// requiring a configuration file.
///
/// # Example
///
/// ```no_run
/// use sift_connect::Credentials;
///
/// // Direct credentials
/// let creds = Credentials::Config {
/// uri: "https://api.siftstack.com".to_string(),
/// apikey: "your-api-key".to_string(),
/// };
///
/// // Profile-based credentials (default profile)
/// let default_profile = Credentials::Profile(None);
///
/// // Profile-based credentials (named profile)
/// let prod_profile = Credentials::Profile(Some("production".to_string()));
/// ```
#[derive(Debug, Clone)]
pub enum Credentials {
/// Load credentials from a named profile in the configuration file.
///
/// If `None`, uses the default (top-level) profile.
Profile(Option<String>),
/// Provide credentials directly.
///
/// Fields:
/// - `uri`: The Sift API endpoint URI
/// - `apikey`: The API key for authentication
Config { uri: String, apikey: String },
}

Expand Down
15 changes: 15 additions & 0 deletions rust/crates/sift_connect/src/grpc/interceptor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
use std::str::FromStr;
use tonic::{Request, Status, metadata::MetadataValue, service::Interceptor};

/// Interceptor that adds authentication headers to gRPC requests.
///
/// This interceptor automatically adds a `Bearer` token authorization header
/// to all outgoing gRPC requests using the provided API key.
///
/// # Example
///
/// ```
/// use sift_connect::grpc::AuthInterceptor;
///
/// let interceptor = AuthInterceptor {
/// apikey: "your-api-key".to_string(),
/// };
/// ```
#[derive(Clone)]
pub struct AuthInterceptor {
/// The API key to use for authentication.
pub apikey: String,
}

Expand Down
226 changes: 216 additions & 10 deletions rust/crates/sift_connect/src/grpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,52 @@ pub mod interceptor;
pub use interceptor::AuthInterceptor;

/// A pre-configured gRPC channel to conveniently establish a connection to Sift's gRPC API.
///
/// This is a type alias for a gRPC channel that has been configured with authentication
/// via [`AuthInterceptor`]. The channel is lazy and won't actually connect until the
/// first RPC call is made.
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
/// use std::env;
///
/// let credentials = Credentials::Config {
/// uri: env::var("SIFT_URI").unwrap(),
/// apikey: env::var("SIFT_API_KEY").unwrap(),
/// };
///
/// let channel: sift_connect::SiftChannel = SiftChannelBuilder::new(credentials)
/// .build()
/// .unwrap();
/// ```
pub type SiftChannel = InterceptedService<Channel, AuthInterceptor>;

/// Used to build an instance of [SiftChannel].
///
/// This builder provides a fluent API for configuring a gRPC channel connection
/// to Sift's API. It supports custom credentials, TLS configuration, and HTTP/2
/// keep-alive settings.
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
/// use std::env;
/// use std::time::Duration;
///
/// let credentials = Credentials::Config {
/// uri: env::var("SIFT_URI").unwrap(),
/// apikey: env::var("SIFT_API_KEY").unwrap(),
/// };
///
/// let channel = SiftChannelBuilder::new(credentials)
/// .use_tls(true)
/// .keep_alive_timeout(Duration::from_secs(30))
/// .build()
/// .unwrap();
/// ```
pub struct SiftChannelBuilder {
credentials: Credentials,
use_tls: bool,
Expand All @@ -32,6 +75,30 @@ pub struct SiftChannelBuilder {

impl SiftChannelBuilder {
/// Initializes a new [SiftChannelBuilder] with sane defaults.
///
/// Default settings:
/// - TLS enabled
/// - Keep-alive while idle enabled
/// - Keep-alive timeout: 20 seconds
/// - Keep-alive interval: 20 seconds
/// - User agent: crate name and version
///
/// # Arguments
///
/// * `credentials` - The credentials to use for authentication
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
///
/// let credentials = Credentials::Config {
/// uri: "https://api.siftstack.com".to_string(),
/// apikey: "your-api-key".to_string(),
/// };
///
/// let builder = SiftChannelBuilder::new(credentials);
/// ```
pub fn new(credentials: Credentials) -> Self {
let crate_name = env!("CARGO_PKG_NAME");
let crate_version = env!("CARGO_PKG_VERSION");
Expand All @@ -47,8 +114,37 @@ impl SiftChannelBuilder {
}
}

/// Consume [SiftChannelBuilder] and return a [SiftChannel]. The [SiftChannel] is lazy and
/// won't actually connect to Sift until the first RPC is made.
/// Consume [SiftChannelBuilder] and return a [SiftChannel].
///
/// The [SiftChannel] is lazy and won't actually connect to Sift until the first
/// RPC is made. This allows you to create the channel early without incurring
/// connection overhead until it's needed.
///
/// # Returns
///
/// A configured [SiftChannel] ready for use with gRPC clients.
///
/// # Errors
///
/// Returns an error if:
/// - The URI is invalid
/// - Credentials cannot be loaded (for profile-based credentials)
/// - TLS configuration fails
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
///
/// let credentials = Credentials::Config {
/// uri: "https://api.siftstack.com".to_string(),
/// apikey: "your-api-key".to_string(),
/// };
///
/// let channel = SiftChannelBuilder::new(credentials)
/// .build()
/// .expect("failed to create channel");
/// ```
pub fn build(self) -> Result<SiftChannel> {
let config::SiftChannelConfig { uri, apikey } =
config::SiftChannelConfig::try_from(self.credentials)?;
Expand Down Expand Up @@ -81,41 +177,151 @@ impl SiftChannelBuilder {
Ok(intercepted_channel)
}

/// Override the default user-agent which is the name of the crate. Do note that the
/// application firewall is sensitive to certain user-agents so if you experience any issues
/// connecting to Sift, please notify the team to ascertain if it's related to a bad
/// user-agent.
/// Override the default user-agent which is the name of the crate.
///
/// The default user-agent is set to `{crate_name}/{crate_version}`. This method
/// allows you to customize it.
///
/// # Note
///
/// The application firewall is sensitive to certain user-agents. If you experience
/// any issues connecting to Sift, please notify the team to ascertain if it's related
/// to a bad user-agent.
///
/// # Arguments
///
/// * `user_agent` - The custom user-agent string to use
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
///
/// let credentials = Credentials::Config {
/// uri: "https://api.siftstack.com".to_string(),
/// apikey: "your-api-key".to_string(),
/// };
///
/// let builder = SiftChannelBuilder::new(credentials)
/// .user_agent("MyApp/1.0");
/// ```
pub fn user_agent<S: AsRef<str>>(mut self, user_agent: S) -> Self {
self.user_agent = user_agent.as_ref().to_string();
self
}

/// Enables/disables TLS. In production, TLS should only ever be enabled. For mocking/testing
/// Enables or disables TLS.
///
/// # Warning
///
/// In production, TLS should only ever be enabled. For mocking/testing
/// purposes, TLS may be disabled.
///
/// # Arguments
///
/// * `use_tls` - Whether to enable TLS encryption
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
///
/// let credentials = Credentials::Config {
/// uri: "https://api.siftstack.com".to_string(),
/// apikey: "your-api-key".to_string(),
/// };
///
/// // Production use - TLS enabled (default)
/// let builder = SiftChannelBuilder::new(credentials.clone())
/// .use_tls(true);
///
/// // Testing use - TLS disabled
/// let test_builder = SiftChannelBuilder::new(credentials)
/// .use_tls(false);
/// ```
pub fn use_tls(mut self, use_tls: bool) -> Self {
self.use_tls = use_tls;
self
}

/// See [`hyper documentation`].
/// Configures whether to send keep-alive pings while the connection is idle.
///
/// See [`hyper documentation`] for detailed information.
///
/// [`hyper documentation`]: https://docs.rs/hyper/latest/hyper/client/conn/http2/struct.Builder.html#method.keep_alive_while_idle
///
/// # Arguments
///
/// * `keep_alive_while_idle` - Whether to send keep-alive pings while idle
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
///
/// # let credentials = Credentials::Config {
/// # uri: "https://api.siftstack.com".to_string(),
/// # apikey: "your-api-key".to_string(),
/// # };
/// let builder = SiftChannelBuilder::new(credentials)
/// .keep_alive_while_idle(true);
/// ```
pub fn keep_alive_while_idle(mut self, keep_alive_while_idle: bool) -> Self {
self.keep_alive_while_idle = keep_alive_while_idle;
self
}

/// See [`hyper documentation`].
/// Configures the timeout for keep-alive pings.
///
/// See [`hyper documentation`] for detailed information.
///
/// [`hyper documentation`]: https://docs.rs/hyper/latest/hyper/client/conn/http2/struct.Builder.html#method.keep_alive_timeout
///
/// # Arguments
///
/// * `keep_alive_timeout` - The timeout duration for keep-alive pings
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
/// use std::time::Duration;
///
/// # let credentials = Credentials::Config {
/// # uri: "https://api.siftstack.com".to_string(),
/// # apikey: "your-api-key".to_string(),
/// # };
/// let builder = SiftChannelBuilder::new(credentials)
/// .keep_alive_timeout(Duration::from_secs(30));
/// ```
pub fn keep_alive_timeout(mut self, keep_alive_timeout: Duration) -> Self {
self.keep_alive_timeout = keep_alive_timeout;
self
}

/// See [`hyper documentation`].
/// Configures the interval between keep-alive pings.
///
/// See [`hyper documentation`] for detailed information.
///
/// [`hyper documentation`]: https://docs.rs/hyper/latest/hyper/client/conn/http2/struct.Builder.html#method.keep_alive_interval
///
/// # Arguments
///
/// * `keep_alive_interval` - The interval duration between keep-alive pings
///
/// # Example
///
/// ```no_run
/// use sift_connect::{Credentials, SiftChannelBuilder};
/// use std::time::Duration;
///
/// # let credentials = Credentials::Config {
/// # uri: "https://api.siftstack.com".to_string(),
/// # apikey: "your-api-key".to_string(),
/// # };
/// let builder = SiftChannelBuilder::new(credentials)
/// .keep_alive_interval(Duration::from_secs(30));
/// ```
pub fn keep_alive_interval(mut self, keep_alive_interval: Duration) -> Self {
self.keep_alive_interval = keep_alive_interval;
self
Expand Down
Loading
Loading