covert_types/
request.rs

1use std::{collections::HashMap, str::FromStr};
2
3use bytes::Bytes;
4use http::{Extensions, Method};
5use http_body::Limited;
6use hyper::Body;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::error::ApiError;
11
12#[derive(Debug)]
13pub struct Request {
14    pub id: Uuid,
15
16    pub operation: Operation,
17
18    pub path: String,
19
20    pub namespace: Vec<String>,
21
22    pub data: Bytes,
23    pub query_string: String,
24    // TODO: don't use this
25    pub extensions: http::Extensions,
26    pub params: Vec<String>,
27    pub token: Option<String>,
28
29    pub headers: HashMap<String, String>,
30}
31
32/// Operation is an enum that is used to specify the type
33/// of request being made
34#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum Operation {
36    // The operations below are called per path
37    Create,
38    Read,
39    Update,
40    Delete,
41    // The operations below are called globally, the path is less relevant.
42    Revoke,
43    Renew,
44}
45
46impl FromStr for Operation {
47    type Err = ApiError;
48
49    fn from_str(s: &str) -> Result<Self, Self::Err> {
50        match &s.to_lowercase()[..] {
51            "create" => Ok(Self::Create),
52            "read" => Ok(Self::Read),
53            "update" => Ok(Self::Update),
54            "delete" => Ok(Self::Delete),
55            "revoke" => Ok(Self::Revoke),
56            "renew" => Ok(Self::Renew),
57            _ => Err(ApiError::bad_request()),
58        }
59    }
60}
61
62impl Request {
63    /// Create a internal logical request from a http request.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if the http request contains unsupported elements that
68    /// cannot be converted to the logical request format.
69    pub async fn new(raw: hyper::Request<Limited<Body>>) -> Result<Self, ApiError> {
70        let uri = raw.uri().clone();
71        let token = raw
72            .headers()
73            .get("X-Covert-Token")
74            .map(|val| val.to_str().unwrap_or_default())
75            .and_then(|token| {
76                if token.is_empty() {
77                    None
78                } else {
79                    Some(token.to_string())
80                }
81            });
82        let namespace = raw
83            .headers()
84            .get("X-Covert-Namespace")
85            .and_then(|namespace| namespace.to_str().ok())
86            .map_or_else(
87                || vec!["root".to_string()],
88                |namespace| {
89                    namespace
90                        .trim()
91                        .to_lowercase()
92                        .split('/')
93                        .map(ToString::to_string)
94                        .filter(|ns| !ns.is_empty())
95                        .collect::<Vec<_>>()
96                },
97            );
98        let headers = raw
99            .headers()
100            .iter()
101            .map(|(name, value)| {
102                (
103                    name.to_string(),
104                    value.to_str().unwrap_or_default().to_string(),
105                )
106            })
107            .collect();
108
109        let operation = match *raw.method() {
110            Method::GET => Operation::Read,
111            Method::POST => Operation::Create,
112            Method::PUT => Operation::Update,
113            Method::DELETE => Operation::Delete,
114            _ => return Err(ApiError::bad_request()),
115        };
116
117        let bytes = hyper::body::to_bytes(raw.into_body())
118            .await
119            .map_err(|_| ApiError::bad_request())?;
120
121        let mut path = uri.path();
122        if path.starts_with("/v1/") {
123            path = &path[4..];
124        }
125
126        Ok(Self {
127            id: Uuid::new_v4(),
128            operation,
129            namespace,
130            query_string: uri.query().unwrap_or_default().to_string(),
131            path: path.to_string(),
132            extensions: Extensions::new(),
133            token,
134            params: vec![],
135            data: bytes,
136            headers,
137        })
138    }
139
140    pub fn operation(&self) -> Operation {
141        self.operation
142    }
143
144    pub fn advance_path(&mut self, prefix: &str) -> bool {
145        if !self.path.starts_with(prefix) {
146            return false;
147        }
148
149        self.path = self.path[prefix.len()..].to_string();
150
151        true
152    }
153}