1use std::fs;
2use std::fs::read_dir;
3use std::future::Future;
4use std::path::PathBuf;
5use std::process::Output;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use regex::Regex;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use tokio::time::Duration;
13
14use crate::common::data::{MockDefinition, MockServerHttpResponse, Pattern, RequestRequirements};
15use crate::common::util::read_file;
16use crate::server::web::handlers::add_new_mock;
17use crate::server::{start_server, MockServerState};
18use crate::Method;
19
20#[derive(Debug, PartialEq, Serialize, Deserialize)]
21pub struct NameValuePair {
22 name: String,
23 value: String,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
27struct YAMLRequestRequirements {
28 pub path: Option<String>,
29 pub path_contains: Option<Vec<String>>,
30 pub path_matches: Option<Vec<String>>,
31 pub method: Option<Method>,
32 pub header: Option<Vec<NameValuePair>>,
33 pub header_exists: Option<Vec<String>>,
34 pub cookie: Option<Vec<NameValuePair>>,
35 pub cookie_exists: Option<Vec<String>>,
36 pub body: Option<String>,
37 pub json_body: Option<Value>,
38 pub json_body_partial: Option<Vec<Value>>,
39 pub body_contains: Option<Vec<String>>,
40 pub body_matches: Option<Vec<String>>,
41 pub query_param_exists: Option<Vec<String>>,
42 pub query_param: Option<Vec<NameValuePair>>,
43 pub x_www_form_urlencoded_key_exists: Option<Vec<String>>,
44 pub x_www_form_urlencoded_tuple: Option<Vec<NameValuePair>>,
45}
46
47#[derive(Debug, Serialize, Deserialize)]
48struct YAMLHTTPResponse {
49 pub status: Option<u16>,
50 pub header: Option<Vec<NameValuePair>>,
51 pub body: Option<String>,
52 pub delay: Option<u64>,
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56struct YAMLMockDefinition {
57 when: YAMLRequestRequirements,
58 then: YAMLHTTPResponse,
59}
60
61pub async fn start_standalone_server<F>(
62 port: u16,
63 expose: bool,
64 static_mock_dir_path: Option<PathBuf>,
65 print_access_log: bool,
66 history_limit: usize,
67 shutdown: F,
68) -> Result<(), String>
69where
70 F: Future<Output = ()>,
71{
72 let state = Arc::new(MockServerState::new(history_limit));
73
74 #[cfg(feature = "standalone")]
75 static_mock_dir_path.map(|path| {
76 read_static_mocks(path)
77 .into_iter()
78 .map(|d| map_to_mock_definition(d))
79 .for_each(|static_mock| {
80 add_new_mock(&state, static_mock, true).expect("cannot add static mock");
81 })
82 });
83
84 start_server(port, expose, &state, None, print_access_log, shutdown).await
85}
86
87#[cfg(feature = "standalone")]
88fn read_static_mocks(path: PathBuf) -> Vec<YAMLMockDefinition> {
89 let mut definitions = Vec::new();
90
91 let paths = read_dir(path).expect("cannot list files in directory");
92 for file_path in paths {
93 let file_path = file_path.unwrap().path();
94 if let Some(ext) = file_path.extension() {
95 if !"yaml".eq(ext) && !"yml".eq(ext) {
96 continue;
97 }
98 }
99
100 log::info!(
101 "Loading static mock file from '{}'",
102 file_path.to_string_lossy()
103 );
104 let content = read_file(file_path).expect("cannot read from file");
105 let content = String::from_utf8(content).expect("cannot convert file content");
106
107 definitions.push(serde_yaml::from_str(&content).unwrap());
108 }
109
110 return definitions;
111}
112
113#[cfg(feature = "standalone")]
114fn map_to_mock_definition(yaml_definition: YAMLMockDefinition) -> MockDefinition {
115 MockDefinition {
116 request: RequestRequirements {
117 path: yaml_definition.when.path,
118 path_contains: yaml_definition.when.path_contains,
119 path_matches: to_pattern_vec(yaml_definition.when.path_matches),
120 method: yaml_definition.when.method.map(|m| m.to_string()),
121 headers: to_pair_vec(yaml_definition.when.header),
122 header_exists: yaml_definition.when.header_exists,
123 cookies: to_pair_vec(yaml_definition.when.cookie),
124 cookie_exists: yaml_definition.when.cookie_exists,
125 body: yaml_definition.when.body,
126 json_body: yaml_definition.when.json_body,
127 json_body_includes: yaml_definition.when.json_body_partial,
128 body_contains: yaml_definition.when.body_contains,
129 body_matches: to_pattern_vec(yaml_definition.when.body_matches),
130 query_param_exists: yaml_definition.when.query_param_exists,
131 query_param: to_pair_vec(yaml_definition.when.query_param),
132 x_www_form_urlencoded: to_pair_vec(yaml_definition.when.x_www_form_urlencoded_tuple),
133 x_www_form_urlencoded_key_exists: yaml_definition.when.x_www_form_urlencoded_key_exists,
134 matchers: None,
135 },
136 response: MockServerHttpResponse {
137 status: yaml_definition.then.status,
138 headers: to_pair_vec(yaml_definition.then.header),
139 body: yaml_definition.then.body.map(|b| b.into_bytes()),
140 delay: yaml_definition.then.delay.map(|v| Duration::from_millis(v)),
141 },
142 }
143}
144
145#[cfg(feature = "standalone")]
146fn to_pattern_vec(vec: Option<Vec<String>>) -> Option<Vec<Pattern>> {
147 vec.map(|vec| {
148 vec.iter()
149 .map(|val| Pattern::from_regex(Regex::from_str(val).expect("cannot parse regex")))
150 .collect()
151 })
152}
153
154#[cfg(feature = "standalone")]
155fn to_pair_vec(kvp: Option<Vec<NameValuePair>>) -> Option<Vec<(String, String)>> {
156 kvp.map(|vec| vec.into_iter().map(|nvp| (nvp.name, nvp.value)).collect())
157}