Skip to content

Commit

Permalink
rust(feature): grpc utils (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
solidiquis authored Nov 8, 2024
1 parent fa4fe4c commit 9cacbd2
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 18 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/rust_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ jobs:
with:
command: check
args: --manifest-path rust/Cargo.toml

- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path rust/Cargo.toml

- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --check --manifest-path rust/Cargo.toml
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ examples/rust/annotations/target/**/*
examples/rust/ingestion_with_config/target/**/*
rust/target/**/*
rust/protos/**/*
rust/examples/**/target/**/*

go/protos/**/*
python/protos/**/*
Expand Down
48 changes: 31 additions & 17 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "sift"
name = "sift_rs"
version = "0.1.0"
edition = "2021"

Expand All @@ -12,7 +12,9 @@ pbjson-types = "0.6.0"
prost = "0.12.4"
prost-types = "0.12.4"
serde = { version = "1.0.203" }
thiserror = "2.0.0"
tonic = { version = "0.11.0", features = ["tls", "tls-roots", "tls-webpki-roots"] }
tower = "0.4.13"

[features]
default = []
121 changes: 121 additions & 0 deletions rust/src/error/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::{error::Error as StdError, fmt, result::Result as StdResult};

pub type Result<T> = StdResult<T, Error>;
pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;

pub(crate) trait SiftError<T, C>
where
C: fmt::Display + Send + Sync + 'static,
{
fn context(self, ctx: C) -> Result<T>;
fn help(self, txt: C) -> Result<T>;
}

/// Error specific to this library
#[derive(Debug)]
pub struct Error {
context: Option<String>,
help: Option<String>,
kind: ErrorKind,
inner: BoxedError,
}

const SPACING: &str = "\n ";

impl Error {
pub fn new_user_error<E>(err: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
let inner = Box::new(err);
Self {
inner,
kind: ErrorKind::User,
context: None,
help: None,
}
}

pub fn new_internal_error<E>(err: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
let inner = Box::new(err);
Self {
inner,
kind: ErrorKind::Internal,
context: None,
help: None,
}
}

pub fn into_inner(self) -> BoxedError {
self.inner
}
}

#[derive(Debug)]
pub enum ErrorKind {
Internal,
User,
}

impl<T, C> SiftError<T, C> for Result<T>
where
C: fmt::Display + Send + Sync + 'static,
{
fn context(self, ctx: C) -> Self {
self.map_err(|mut err| {
err.context = Some(format!("{ctx}"));
err
})
}

fn help(self, txt: C) -> Self {
self.map_err(|mut err| {
err.help = Some(format!("{txt}"));
err
})
}
}

impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Internal => writeln!(f, "Internal error"),
Self::User => writeln!(f, "User error"),
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Error {
context,
kind,
help,
inner,
} = self;

match (context, help) {
(Some(ctx), Some(help_txt)) => {
writeln!(
f,
"({kind}) {ctx}{SPACING}[cause]: {inner}{SPACING}[help]: {help_txt}"
)
}
(Some(ctx), None) => {
writeln!(f, "({kind}) {ctx}{SPACING}[cause]: {inner}{SPACING}")
}
(None, Some(help_txt)) => {
writeln!(
f,
"({kind}){SPACING}[cause]: {inner}{SPACING}[help]: {help_txt}"
)
}
(None, None) => {
writeln!(f, "({kind}){SPACING}[cause]: {inner}")
}
}
}
}
13 changes: 13 additions & 0 deletions rust/src/grpc/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub struct SiftChannelConfig {
pub uri: String,
pub apikey: String,
}

impl SiftChannelConfig {
pub fn new(uri: &str, apikey: &str) -> Self {
Self {
uri: uri.to_string(),
apikey: apikey.to_string(),
}
}
}
18 changes: 18 additions & 0 deletions rust/src/grpc/interceptor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::str::FromStr;
use tonic::{metadata::MetadataValue, service::Interceptor, Request, Status};

#[derive(Clone)]
pub struct AuthInterceptor {
pub apikey: String,
}

impl Interceptor for AuthInterceptor {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
let auth_token = format!("Bearer {}", &self.apikey);
let apikey = MetadataValue::from_str(&auth_token)
.map_err(|e| Status::invalid_argument(format!("failed to parse API key: {e}")))?;

request.metadata_mut().insert("authorization", apikey);
Ok(request)
}
}
33 changes: 33 additions & 0 deletions rust/src/grpc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::error::{Error, Result, SiftError};
use tonic::{
service::interceptor::InterceptedService,
transport::channel::{Channel, Endpoint},
};
use tower::ServiceBuilder;

mod config;
use config::SiftChannelConfig;

mod interceptor;
use interceptor::AuthInterceptor;

/// A pre-configured gRPC channel to conveniently establish a connection to Sift's gRPC API.
pub type SiftChannel = InterceptedService<Channel, AuthInterceptor>;

/// Uses `channel_config` to initialize a lazy channel that will only establish a connection
/// after first-use.
pub fn use_sift_channel(channel_config: SiftChannelConfig) -> Result<SiftChannel> {
let SiftChannelConfig { uri, apikey } = channel_config;

let channel = Endpoint::from_shared(uri)
.map_err(Error::new_user_error)
.context("something went while trying to establish a connection to Sift")
.help("double check that the URL and the API token are both valid")?
.connect_lazy();

let intercepted_channel = ServiceBuilder::new()
.layer(tonic::service::interceptor(AuthInterceptor { apikey }))
.service(channel);

Ok(intercepted_channel)
}
10 changes: 10 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
#[allow(clippy::all)]
/// Protobuf generated code to interface with Sift's gRPC API.
pub mod gen;

/// Preconfigured gRPC utilities.
pub mod grpc;

/// Error types specific for this library. Note that when using the `gen` module
/// errors may occur that are not accounted for in this module.
pub mod error;
pub use error::Result;

0 comments on commit 9cacbd2

Please sign in to comment.