|
17 | 17 | */
|
18 | 18 |
|
19 | 19 | use actix_web::web;
|
20 |
| -use actix_web::HttpRequest; |
21 | 20 | use chrono::{Date, DateTime, Timelike, Utc};
|
22 | 21 | use serde_json::{json, Value};
|
23 | 22 | use std::collections::HashMap;
|
24 | 23 |
|
25 | 24 | use crate::Error;
|
26 | 25 |
|
27 |
| -const META_LABEL: &str = "x-p-meta"; |
28 |
| - |
29 |
| -pub fn flatten_json_body( |
30 |
| - body: web::Json<serde_json::Value>, |
31 |
| - labels: Option<String>, |
32 |
| -) -> Result<String, Error> { |
33 |
| - let mut collector_labels = HashMap::new(); |
34 |
| - |
35 |
| - collector_labels.insert("labels".to_string(), labels.unwrap()); |
36 |
| - |
| 26 | +pub fn flatten_json_body(body: web::Json<serde_json::Value>) -> Result<String, Error> { |
37 | 27 | let mut flat_value: Value = json!({});
|
38 |
| - let new_body = merge(&body, &collector_labels); |
39 |
| - flatten_json::flatten(&new_body, &mut flat_value, None, true, Some("_")).unwrap(); |
| 28 | + flatten_json::flatten(&body, &mut flat_value, None, true, Some("_")).unwrap(); |
40 | 29 | let flattened = serde_json::to_string(&flat_value)?;
|
41 | 30 |
|
42 | 31 | Ok(flattened)
|
43 | 32 | }
|
44 | 33 |
|
45 |
| -fn merge(v: &Value, fields: &HashMap<String, String>) -> Value { |
46 |
| - match v { |
47 |
| - Value::Object(m) => { |
48 |
| - let mut m = m.clone(); |
| 34 | +pub fn merge(value: Value, fields: HashMap<String, String>) -> Value { |
| 35 | + match value { |
| 36 | + Value::Object(mut m) => { |
49 | 37 | for (k, v) in fields {
|
50 |
| - match m.get(k) { |
51 |
| - Some(curr_val) => { |
| 38 | + match m.get_mut(&k) { |
| 39 | + Some(val) => { |
52 | 40 | let mut final_val = String::new();
|
53 |
| - final_val.push_str(curr_val.as_str().unwrap()); |
| 41 | + final_val.push_str(val.as_str().unwrap()); |
54 | 42 | final_val.push(',');
|
55 |
| - final_val.push_str(v); |
56 |
| - m.insert(k.clone(), Value::String(final_val)); |
| 43 | + final_val.push_str(&v); |
| 44 | + *val = Value::String(final_val); |
57 | 45 | }
|
58 | 46 | None => {
|
59 |
| - m.insert(k.clone(), Value::String(v.to_string())); |
| 47 | + m.insert(k, Value::String(v)); |
60 | 48 | }
|
61 | 49 | }
|
62 | 50 | }
|
63 | 51 | Value::Object(m)
|
64 | 52 | }
|
65 |
| - v => v.clone(), |
| 53 | + value => value, |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +pub mod header_parsing { |
| 58 | + const MAX_HEADERS_ALLOWED: usize = 5; |
| 59 | + use actix_web::{HttpRequest, HttpResponse, ResponseError}; |
| 60 | + |
| 61 | + pub fn collect_labelled_headers( |
| 62 | + req: &HttpRequest, |
| 63 | + prefix: &str, |
| 64 | + kv_separator: char, |
| 65 | + ) -> Result<String, ParseHeaderError> { |
| 66 | + // filter out headers which has right prefix label and convert them into str; |
| 67 | + let headers = req.headers().iter().filter_map(|(key, value)| { |
| 68 | + let key = key.as_str().strip_prefix(prefix)?; |
| 69 | + Some((key, value)) |
| 70 | + }); |
| 71 | + |
| 72 | + let mut labels: Vec<String> = Vec::new(); |
| 73 | + |
| 74 | + for (key, value) in headers { |
| 75 | + let value = value.to_str().map_err(|_| ParseHeaderError::InvalidValue)?; |
| 76 | + if key.is_empty() { |
| 77 | + return Err(ParseHeaderError::Emptykey); |
| 78 | + } |
| 79 | + if key.contains(kv_separator) { |
| 80 | + return Err(ParseHeaderError::SeperatorInKey(kv_separator)); |
| 81 | + } |
| 82 | + if value.contains(kv_separator) { |
| 83 | + return Err(ParseHeaderError::SeperatorInValue(kv_separator)); |
| 84 | + } |
| 85 | + |
| 86 | + labels.push(format!("{}={}", key, value)); |
| 87 | + } |
| 88 | + |
| 89 | + if labels.len() > MAX_HEADERS_ALLOWED { |
| 90 | + return Err(ParseHeaderError::MaxHeadersLimitExceeded); |
| 91 | + } |
| 92 | + |
| 93 | + Ok(labels.join(",")) |
| 94 | + } |
| 95 | + |
| 96 | + #[derive(Debug, thiserror::Error)] |
| 97 | + pub enum ParseHeaderError { |
| 98 | + #[error("Too many headers received. Limit is of 5 headers")] |
| 99 | + MaxHeadersLimitExceeded, |
| 100 | + #[error("A value passed in header is not formattable to plain visible ASCII")] |
| 101 | + InvalidValue, |
| 102 | + #[error("Invalid Key was passed which terminated just after the end of prefix")] |
| 103 | + Emptykey, |
| 104 | + #[error("A key passed in header contains reserved char {0}")] |
| 105 | + SeperatorInKey(char), |
| 106 | + #[error("A value passed in header contains reserved char {0}")] |
| 107 | + SeperatorInValue(char), |
| 108 | + } |
| 109 | + |
| 110 | + impl ResponseError for ParseHeaderError { |
| 111 | + fn status_code(&self) -> http::StatusCode { |
| 112 | + http::StatusCode::BAD_REQUEST |
| 113 | + } |
| 114 | + |
| 115 | + fn error_response(&self) -> HttpResponse { |
| 116 | + HttpResponse::build(self.status_code()).body(self.to_string()) |
| 117 | + } |
66 | 118 | }
|
67 | 119 | }
|
68 | 120 |
|
@@ -110,25 +162,6 @@ pub fn minute_to_prefix(minute: u32, data_granularity: u32) -> Option<String> {
|
110 | 162 | ))
|
111 | 163 | }
|
112 | 164 |
|
113 |
| -/// collect labels passed from http headers |
114 |
| -/// format: labels = "app=k8s, cloud=gcp" |
115 |
| -pub fn collect_labels(req: &HttpRequest) -> Option<String> { |
116 |
| - let keys = req.headers().keys().cloned().collect::<Vec<_>>(); |
117 |
| - |
118 |
| - let mut labels_vec = Vec::with_capacity(100); |
119 |
| - for key in keys { |
120 |
| - if key.to_string().to_lowercase().starts_with(META_LABEL) { |
121 |
| - let value = req.headers().get(&key)?.to_str().ok(); |
122 |
| - let remove_meta_char = format!("{}-", META_LABEL); |
123 |
| - let kv = |
124 |
| - format! {"{}={}", key.to_string().replace(&remove_meta_char, ""), value.unwrap()}; |
125 |
| - labels_vec.push(kv); |
126 |
| - } |
127 |
| - } |
128 |
| - |
129 |
| - Some(labels_vec.join(",")) |
130 |
| -} |
131 |
| - |
132 | 165 | pub struct TimePeriod {
|
133 | 166 | start: DateTime<Utc>,
|
134 | 167 | end: DateTime<Utc>,
|
|
0 commit comments