configuration.rs
1 use apibara_sink_common::SinkOptions; 2 use clap::Args; 3 use error_stack::{Result, ResultExt}; 4 use http::{HeaderMap, HeaderName, HeaderValue, Uri}; 5 use serde::Deserialize; 6 7 use crate::sink::SinkWebhookError; 8 9 #[derive(Debug)] 10 pub struct SinkWebhookConfiguration { 11 pub target_url: Uri, 12 pub headers: HeaderMap, 13 pub raw: bool, 14 } 15 16 #[derive(Debug, Args, Default, SinkOptions)] 17 #[sink_options(tag = "webhook")] 18 pub struct SinkWebhookOptions { 19 /// The target url to send the request to. 20 #[arg(long, env = "WEBHOOK_TARGET_URL")] 21 target_url: Option<String>, 22 23 /// Additional headers to send with the request. 24 #[arg(long, short = 'H', value_delimiter = ',', env = "WEBHOOK_HEADERS")] 25 header: Option<Vec<String>>, 26 27 /// Send the data received from the transform step as is. 28 /// 29 /// Use this to interact with any API like Discord or Telegram. 30 #[arg(long, action, env = "WEBHOOK_RAW")] 31 raw: Option<bool>, 32 } 33 34 impl SinkOptions for SinkWebhookOptions { 35 fn merge(self, other: SinkWebhookOptions) -> Self { 36 Self { 37 target_url: self.target_url.or(other.target_url), 38 header: self.header.or(other.header), 39 raw: self.raw.or(other.raw), 40 } 41 } 42 } 43 44 impl SinkWebhookOptions { 45 pub fn to_webhook_configuration(self) -> Result<SinkWebhookConfiguration, SinkWebhookError> { 46 let target_url = self 47 .target_url 48 .ok_or(SinkWebhookError) 49 .attach_printable("missing target url")? 50 .parse::<Uri>() 51 .change_context(SinkWebhookError) 52 .attach_printable("malformed target url")?; 53 54 let headers = match self.header { 55 None => HeaderMap::new(), 56 Some(headers) => parse_headers(&headers)?, 57 }; 58 59 Ok(SinkWebhookConfiguration { 60 target_url, 61 headers, 62 raw: self.raw.unwrap_or(false), 63 }) 64 } 65 } 66 67 fn parse_headers(headers: &[String]) -> Result<HeaderMap, SinkWebhookError> { 68 let mut new_headers = HeaderMap::new(); 69 for header in headers { 70 match header.split_once(':') { 71 None => { 72 return Err(SinkWebhookError) 73 .attach_printable("header not in the `key: value` format") 74 } 75 Some((name, value)) => { 76 let name = name 77 .parse::<HeaderName>() 78 .change_context(SinkWebhookError) 79 .attach_printable("failed to parse header name")?; 80 let value = value 81 .parse::<HeaderValue>() 82 .change_context(SinkWebhookError) 83 .attach_printable("failed to parse header value")?; 84 new_headers.append(name, value); 85 } 86 } 87 } 88 89 Ok(new_headers) 90 }