summa_core/directories/
external_requests.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fmt::Debug;
4use std::io;
5use std::marker::PhantomData;
6use std::ops::Range;
7use std::path::PathBuf;
8use strfmt::strfmt;
9use summa_proto::proto::RemoteEngineConfig;
10
11use crate::errors::ValidationError;
12use crate::Error;
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
15#[serde(rename_all = "snake_case")]
16pub struct Header {
17 pub name: String,
18 pub value: String,
19}
20
21impl Header {
22 pub fn new(name: &str, value: &str) -> Header {
23 Header {
24 name: name.to_string(),
25 value: value.to_string(),
26 }
27 }
28}
29
30#[derive(Debug, Deserialize, Serialize)]
32#[serde(rename_all = "snake_case")]
33pub struct ExternalResponse {
34 #[serde(with = "serde_bytes")]
35 pub data: Vec<u8>,
36 pub headers: Vec<Header>,
37}
38
39#[derive(thiserror::Error, Debug)]
40pub enum RequestError {
41 #[error("external: {0}")]
42 External(String),
43 #[error("io_error: {0} {1}")]
44 IoError(io::Error, PathBuf),
45 #[error("not_found: {0}")]
46 NotFound(PathBuf),
47 #[cfg(feature = "external-request")]
48 #[error("hyper_error: {0}")]
49 Hyper(#[from] hyper::Error),
50 #[cfg(feature = "external-request")]
51 #[error("hyper_http_error: {0}")]
52 HyperHttp(#[from] hyper::http::Error),
53 #[cfg(feature = "external-request")]
54 #[error("invalid_method: {0}")]
55 InvalidMethod(#[from] hyper::http::method::InvalidMethod),
56}
57
58#[async_trait]
59pub trait ExternalRequest: Debug + Send + Sync {
60 fn new(method: &str, url: &str, headers: &[Header]) -> Self
61 where
62 Self: Sized;
63 fn request(self) -> Result<ExternalResponse, RequestError>;
64 async fn request_async(self) -> Result<ExternalResponse, RequestError>;
65 fn url(&self) -> &str;
66}
67
68pub trait ExternalRequestGenerator<TExternalRequest: ExternalRequest>: ExternalRequestGeneratorClone<TExternalRequest> + Debug + Send + Sync {
69 fn new(network_config: RemoteEngineConfig) -> Self
70 where
71 Self: Sized;
72 fn generate_range_request(&self, file_name: &str, range: Option<Range<u64>>) -> TExternalRequest;
73 fn generate_length_request(&self, file_name: &str) -> TExternalRequest;
74}
75
76pub trait ExternalRequestGeneratorClone<TExternalRequest: ExternalRequest> {
77 fn box_clone(&self) -> Box<dyn ExternalRequestGenerator<TExternalRequest>>;
78}
79
80#[derive(Clone, Debug)]
81pub struct DefaultExternalRequestGenerator<TExternalRequest: ExternalRequest + Clone> {
82 remote_engine_config: RemoteEngineConfig,
83 _pd: PhantomData<TExternalRequest>,
84}
85
86impl<TExternalRequest: ExternalRequest + Clone + 'static> ExternalRequestGeneratorClone<TExternalRequest>
87 for DefaultExternalRequestGenerator<TExternalRequest>
88{
89 fn box_clone(&self) -> Box<dyn ExternalRequestGenerator<TExternalRequest>> {
90 Box::new((*self).clone())
91 }
92}
93
94impl<TExternalRequest: ExternalRequest + Clone + 'static> ExternalRequestGenerator<TExternalRequest> for DefaultExternalRequestGenerator<TExternalRequest> {
95 fn new(remote_engine_config: RemoteEngineConfig) -> DefaultExternalRequestGenerator<TExternalRequest> {
96 DefaultExternalRequestGenerator {
97 remote_engine_config,
98 _pd: PhantomData,
99 }
100 }
101
102 fn generate_range_request(&self, file_name: &str, range: Option<Range<u64>>) -> TExternalRequest {
103 let mut vars = HashMap::new();
104 vars.insert("file_name".to_string(), file_name.to_string());
105 if let Some(range) = &range {
106 let start = range.start.to_string();
107 let end = (range.end - 1).to_string();
108 vars.insert("start".to_string(), start);
109 vars.insert("end".to_string(), end);
110 } else {
111 vars.insert("start".to_string(), "0".to_string());
112 vars.insert("end".to_string(), "".to_string());
113 }
114
115 let mut headers = Vec::with_capacity(self.remote_engine_config.headers_template.len());
116 for (header_name, header_value) in self.remote_engine_config.headers_template.iter() {
117 if range.is_none() && header_name == "range" {
119 continue;
120 }
121 headers.push(Header {
122 name: header_name.clone(),
123 value: strfmt(header_value, &vars)
124 .map_err(|e| Error::Validation(Box::new(ValidationError::from(e))))
125 .expect("broken fmt"),
126 });
127 }
128 TExternalRequest::new(
129 &self.remote_engine_config.method,
130 &strfmt(&self.remote_engine_config.url_template, &vars)
131 .map_err(|e| Error::Validation(Box::new(ValidationError::from(e))))
132 .expect("broken fmt"),
133 &headers,
134 )
135 }
136
137 fn generate_length_request(&self, file_name: &str) -> TExternalRequest {
138 let mut vars = HashMap::new();
139 vars.insert("file_name".to_string(), file_name);
140 TExternalRequest::new(
141 "HEAD",
142 &strfmt(&self.remote_engine_config.url_template, &vars)
143 .map_err(|e| Error::Validation(Box::new(ValidationError::from(e))))
144 .expect("broken fmt"),
145 &[],
146 )
147 }
148}