/ sinks / sink-webhook / src / configuration.rs
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  }