subgraph_mock/state/
config.rs

1use crate::{
2    handle::graphql::ResponseGenerationConfig,
3    latency::{LatencyConfig, LatencyGenerator},
4};
5use anyhow::Error;
6use hyper::{
7    HeaderMap,
8    header::{HeaderName, HeaderValue},
9};
10use serde::{Deserialize, Serialize};
11use serde_json_bytes::serde_json;
12use serde_yaml::Value;
13use std::collections::HashMap;
14use tracing::{info, warn};
15
16/// Allowed in the YAML, but not represented in the [BaseConfig] struct as we
17/// neither want nor need that data structure to be recursive.
18const SUBGRAPH_OVERRIDES_KEY: &str = "subgraph_overrides";
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21struct BaseConfig {
22    #[serde(default = "default_port")]
23    pub port: u16,
24    #[serde(default)]
25    pub headers: HashMap<String, String>,
26    #[serde(default)]
27    pub latency: LatencyConfig,
28    #[serde(default)]
29    pub response_generation: ResponseGenerationConfig,
30    #[serde(default = "default_cache_responses")]
31    pub cache_responses: bool,
32}
33
34pub fn default_port() -> u16 {
35    8080
36}
37
38fn default_cache_responses() -> bool {
39    true
40}
41
42impl Default for BaseConfig {
43    fn default() -> Self {
44        Self {
45            port: default_port(),
46            headers: Default::default(),
47            latency: Default::default(),
48            response_generation: Default::default(),
49            cache_responses: default_cache_responses(),
50        }
51    }
52}
53
54impl BaseConfig {
55    pub fn into_parts(
56        self,
57    ) -> anyhow::Result<(
58        u16,
59        bool,
60        LatencyGenerator,
61        HeaderMap<HeaderValue>,
62        ResponseGenerationConfig,
63    )> {
64        info!(config=%serde_json::to_string(&self.latency).unwrap(), "latency generation");
65        let latency_generator = LatencyGenerator::new(self.latency);
66
67        info!(headers=%serde_json::to_string(&self.headers).unwrap(), "additional headers");
68        let additional_headers: anyhow::Result<HeaderMap<HeaderValue>> = self
69            .headers
70            .into_iter()
71            .map(|(k, v)| Ok((HeaderName::try_from(&k)?, HeaderValue::try_from(&v)?)))
72            .collect();
73
74        let mut response_generation = self.response_generation;
75        response_generation.merge_default_scalars();
76
77        info!(config=%serde_json::to_string(&response_generation).unwrap(), "response generation");
78
79        Ok((
80            self.port,
81            self.cache_responses,
82            latency_generator,
83            additional_headers?,
84            response_generation,
85        ))
86    }
87}
88
89#[derive(Debug, Clone)]
90pub struct Config {
91    pub headers: HeaderMap<HeaderValue>,
92    pub latency_generator: LatencyGenerator,
93    pub response_generation: ResponseGenerationConfig,
94    pub cache_responses: bool,
95    pub subgraph_overrides: SubgraphOverrides,
96}
97
98#[derive(Debug, Clone, Default)]
99pub struct SubgraphOverrides {
100    pub headers: HashMap<String, HeaderMap<HeaderValue>>,
101    pub latency_generator: HashMap<String, LatencyGenerator>,
102    pub response_generation: HashMap<String, ResponseGenerationConfig>,
103    pub cache_responses: HashMap<String, bool>,
104}
105
106impl Default for Config {
107    fn default() -> Self {
108        Self {
109            headers: Default::default(),
110            latency_generator: LatencyGenerator::new(LatencyConfig::default()),
111            response_generation: Default::default(),
112            cache_responses: default_cache_responses(),
113            subgraph_overrides: Default::default(),
114        }
115    }
116}
117
118impl Config {
119    /// Parses a YAML file into a resolved port and [Config]
120    pub fn parse_yaml(mut base: Value) -> anyhow::Result<(u16, Config)> {
121        let mapping = base
122            .as_mapping_mut()
123            .ok_or_else(|| Error::msg("config file must be a mapping"))?;
124
125        let mut subgraph_cache_responses = HashMap::new();
126        let mut subgraph_headers = HashMap::new();
127        let mut subgraph_latency_generators = HashMap::new();
128        let mut subgraph_response_generation_configs = HashMap::new();
129
130        if let Some(overrides) = mapping.remove(SUBGRAPH_OVERRIDES_KEY) {
131            match overrides {
132                Value::Mapping(mapping) => {
133                    for (subgraph_name, subgraph_override) in mapping {
134                        let mut subgraph_config = base.clone();
135
136                        let override_mapping = subgraph_override
137                            .as_mapping()
138                            .ok_or_else(|| Error::msg("subgraph override must be a mapping"))?;
139
140                        if override_mapping.contains_key("port") {
141                            warn!("port overrides for subgraphs will be ignored")
142                        }
143
144                        merge_yaml(subgraph_override, &mut subgraph_config);
145                        let parsed_config: BaseConfig = serde_yaml::from_value(subgraph_config)?;
146                        let subgraph_name: String = serde_yaml::from_value(subgraph_name)?;
147
148                        info!("generating customized config for {}", subgraph_name);
149                        let (
150                            _port,
151                            cache_responses,
152                            latency_generator,
153                            headers,
154                            response_generation,
155                        ) = parsed_config.into_parts()?;
156
157                        subgraph_cache_responses.insert(subgraph_name.clone(), cache_responses);
158                        subgraph_latency_generators
159                            .insert(subgraph_name.clone(), latency_generator);
160                        subgraph_headers.insert(subgraph_name.clone(), headers);
161                        subgraph_response_generation_configs
162                            .insert(subgraph_name, response_generation);
163                    }
164                }
165                _ => return Err(Error::msg("config file must be a mapping")),
166            }
167        }
168
169        let (port, cache_responses, latency, headers, response_generation) =
170            serde_yaml::from_value::<BaseConfig>(base)?.into_parts()?;
171
172        Ok((
173            port,
174            Config {
175                headers,
176                latency_generator: latency,
177                response_generation,
178                cache_responses,
179                subgraph_overrides: SubgraphOverrides {
180                    headers: subgraph_headers,
181                    latency_generator: subgraph_latency_generators,
182                    response_generation: subgraph_response_generation_configs,
183                    cache_responses: subgraph_cache_responses,
184                },
185            },
186        ))
187    }
188}
189
190/// A function for merging yaml overrides with the base config.
191/// It does *not* combine arrays, since arrays are effectively scalar values that should be replaced, not merged,
192/// in the context of the subgraph config. We may also want to revisit the mapping merge logic if it ends up being
193/// unintuitive in the context of configuration such as the latency waveforms.
194fn merge_yaml(overrides: serde_yaml::Value, base: &mut serde_yaml::Value) {
195    use serde_yaml::Value;
196
197    match (overrides, base) {
198        // If both values are mappings we add all keys from src into dst.
199        (Value::Mapping(override_map), Value::Mapping(base_map)) => {
200            for (key, override_val) in override_map.into_iter() {
201                // If a key is present in both maps then we recursively merge the values,
202                // otherwise we just insert the src key into dst directly.
203                match base_map.get_mut(&key) {
204                    Some(base_val) => merge_yaml(override_val, base_val),
205                    None => _ = base_map.insert(key, override_val),
206                };
207            }
208        }
209
210        // Otherwise we replace base with overrides
211        (overrides, base) => *base = overrides,
212    }
213}