zino_http/response/
webhook.rs1use crate::helper;
2use http::{
3 Method,
4 header::{HeaderMap, HeaderName},
5};
6use serde::{Serialize, de::DeserializeOwned};
7use serde_json::value::RawValue;
8use toml::Table;
9use url::Url;
10use zino_core::{
11 JsonValue, Map,
12 application::Agent,
13 bail,
14 error::Error,
15 extension::{HeaderMapExt, JsonObjectExt, JsonValueExt, TomlTableExt, TomlValueExt},
16 trace::TraceContext,
17};
18
19pub struct WebHook {
21 name: String,
23 method: Method,
25 base_url: Url,
27 query: Map,
29 headers: Map,
31 body: Option<Box<RawValue>>,
33 params: Option<Map>,
35}
36
37impl WebHook {
38 pub fn try_new(config: &Table) -> Result<Self, Error> {
40 let name = config.get_str("name").unwrap_or("webhook");
41 let method = if let Some(method) = config.get_str("method") {
42 method.parse()?
43 } else {
44 Method::GET
45 };
46 let mut base_url = if let Some(base_url) = config.get_str("base-url") {
47 base_url.parse::<Url>()?
48 } else {
49 bail!("base URL should be specified");
50 };
51 if let Some(query) = config.get_table("query") {
52 let query = serde_qs::to_string(query)?;
53 base_url.set_query(Some(&query));
54 }
55
56 let headers = config
57 .get("headers")
58 .and_then(|v| v.to_json_value().into_map_opt())
59 .unwrap_or_default();
60 let body = if let Some(body) = config.get_table("body") {
61 Some(serde_json::value::to_raw_value(body)?)
62 } else {
63 None
64 };
65 let params = config
66 .get("params")
67 .and_then(|v| v.to_json_value().into_map_opt());
68 Ok(Self {
69 name: name.to_owned(),
70 method,
71 base_url,
72 query: Map::new(),
73 headers,
74 body,
75 params,
76 })
77 }
78
79 #[inline]
81 pub fn query(mut self, key: &str, value: impl Into<JsonValue>) -> Self {
82 let value = value.into();
83 if !value.is_null() {
84 self.query.upsert(key, value);
85 }
86 self
87 }
88
89 #[inline]
91 pub fn query_param(mut self, key: &str, param: Option<&str>) -> Self {
92 if let Some(param) = param {
93 self.query.upsert(key, ["${", param, "}"].concat());
94 } else {
95 self.query.upsert(key, ["${", key, "}"].concat());
96 }
97 self
98 }
99
100 pub fn build_query(mut self) -> Result<Self, Error> {
102 if !self.query.is_empty() {
103 let query = serde_qs::to_string(&self.query)?;
104 self.base_url.set_query(Some(&query));
105 self.query.clear();
106 }
107 Ok(self)
108 }
109
110 #[inline]
112 pub fn header(mut self, key: &str, value: impl Into<JsonValue>) -> Self {
113 self.headers.upsert(key, value);
114 self
115 }
116
117 #[inline]
119 pub fn set_query<T: Serialize>(&mut self, query: &T) {
120 if let Ok(query) = serde_qs::to_string(query) {
121 self.base_url.set_query(Some(&query));
122 }
123 }
124
125 #[inline]
127 pub fn set_body<T: Serialize>(&mut self, body: &T) {
128 self.body = serde_json::value::to_raw_value(body).ok();
129 }
130
131 #[inline]
133 pub fn set_params(&mut self, params: impl Into<JsonValue>) {
134 self.params = params.into().into_map_opt();
135 }
136
137 #[inline]
139 pub fn name(&self) -> &str {
140 &self.name
141 }
142
143 pub async fn trigger<T: DeserializeOwned>(&self) -> Result<T, Error> {
145 let params = self.params.as_ref();
146 let mut url = self.base_url.clone();
147 if !self.query.is_empty() {
148 let query = serde_qs::to_string(&self.query)?;
149 url.set_query(Some(&query));
150 }
151
152 let url = percent_encoding::percent_decode_str(url.as_str()).decode_utf8()?;
153 let resource = helper::format_query(&url, params);
154 let mut options = Map::from_entry("method", self.method.as_str());
155 if let Some(body) = self.body.as_deref().map(|v| v.get()) {
156 options.upsert("body", helper::format_query(body, params));
157 }
158
159 let mut headers = HeaderMap::new();
160 for (key, value) in self.headers.iter() {
161 if let Ok(header_name) = HeaderName::try_from(key) {
162 let header_value = value
163 .as_str()
164 .and_then(|s| helper::format_query(s, params).parse().ok());
165 if let Some(header_value) = header_value {
166 headers.insert(header_name, header_value);
167 }
168 }
169 }
170
171 let mut trace_context = TraceContext::new();
172 trace_context.record_trace_state();
173
174 let response = Agent::request_builder(resource.as_ref(), Some(&options))?
175 .headers(headers)
176 .header("traceparent", trace_context.traceparent())
177 .header("tracestate", trace_context.tracestate())
178 .send()
179 .await?
180 .error_for_status()?;
181 let data = if response.headers().has_json_content_type() {
182 response.json().await?
183 } else {
184 let text = response.text().await?;
185 serde_json::from_str(&text)?
186 };
187 Ok(data)
188 }
189}