endpoint_validator/parser/
services.rs1use crate::parser::{
2 EndpointData, EndpointMetadata, ParamValue, ParameterMetadata, Services, Type,
3};
4use anyhow::{Context, Result, anyhow, bail};
5use serde_json::{Number, Value};
6use std::collections::HashMap;
7
8pub trait ConvertValue {
13 fn convert_value(&self, value: &str) -> Result<Value>;
14}
15
16impl Services {
17 pub fn extract_endpoints(&self) -> (Vec<String>, HashMap<String, EndpointMetadata>) {
18 let mut endpoint_names = Vec::new();
19 let mut endpoint_data = HashMap::new();
20
21 for service in &self.services {
22 for endpoint in &service.endpoints {
23 endpoint_names.push(endpoint.name.clone());
24
25 let param_names_and_types = endpoint
26 .parameters
27 .iter()
28 .map(|param| ParameterMetadata {
29 name: param.name.clone(),
30 ty: param.ty.clone(),
31 })
32 .collect();
33
34 let returns_stream = endpoint.stream_response.is_some();
35
36 let metadata = EndpointMetadata {
37 service_name: service.name.clone(),
38 method_id: endpoint.code,
39 params: param_names_and_types,
40 is_stream: returns_stream,
41 };
42
43 endpoint_data.insert(endpoint.name.clone(), metadata);
44 }
45 }
46
47 (endpoint_names, endpoint_data)
48 }
49}
50
51impl ConvertValue for Type {
52 fn convert_value(&self, value: &str) -> Result<Value> {
58 Ok(match self {
59 Type::String
60 | Type::UUID
61 | Type::Bytea
62 | Type::IpAddr
63 | Type::NanoId { .. }
64 | Type::BlockchainDecimal
65 | Type::BlockchainAddress
66 | Type::BlockchainTransactionHash => Value::String(value.to_string()),
67
68 Type::UInt32 => Value::Number(Number::from(value.parse::<u32>()?)),
69 Type::Int32 => Value::Number(Number::from(value.parse::<i32>()?)),
70 Type::Int64 | Type::TimeStampMs => Value::Number(Number::from(value.parse::<i64>()?)),
71 Type::Float64 => Value::Number(
72 Number::from_f64(value.parse::<f64>()?)
73 .ok_or_else(|| anyhow!("`{value}` is not a finite number"))?,
74 ),
75 Type::Boolean => Value::Bool(value.parse::<bool>()?),
76 Type::Unit => Value::Null,
77 Type::Object => serde_json::from_str(value)
78 .with_context(|| format!("`{value}` is not valid JSON for an Object field"))?,
79
80 Type::Optional(inner) => {
81 if value.is_empty() {
82 Value::Null
83 } else {
84 inner.convert_value(value)?
85 }
86 }
87 Type::Vec(inner) => Value::Array(
88 split_top_level(value, ',')
89 .iter()
90 .map(|v| inner.convert_value(v))
91 .collect::<Result<Vec<_>>>()?,
92 ),
93
94 Type::Struct { name, fields } => {
95 let supplied: HashMap<&str, &str> = split_top_level(value, ',')
96 .into_iter()
97 .filter_map(|pair| pair.split_once(':'))
98 .map(|(k, v)| (k.trim(), v.trim()))
99 .collect();
100
101 let mut map = serde_json::Map::new();
102 for field in fields {
103 match supplied.get(field.name.as_str()) {
104 Some(v) => {
105 map.insert(field.name.clone(), field.ty.convert_value(v)?);
106 }
107 None if matches!(field.ty, Type::Optional(_)) => {}
110 None => bail!("struct `{name}`: missing required field `{}`", field.name),
111 }
112 }
113 Value::Object(map)
114 }
115
116 Type::Enum { name, variants } => {
120 if let Some(variant) = variants.iter().find(|v| v.name == value) {
121 Value::Number(Number::from(variant.value))
122 } else if let Ok(n) = value.parse::<i64>() {
123 if variants.iter().any(|v| v.value == n) {
124 Value::Number(Number::from(n))
125 } else {
126 bail!("enum `{name}`: no variant has value {n}");
127 }
128 } else {
129 bail!(
130 "enum `{name}`: `{value}` is not a variant. Expected one of: {}",
131 variants
132 .iter()
133 .map(|v| v.name.as_str())
134 .collect::<Vec<_>>()
135 .join(", ")
136 );
137 }
138 }
139
140 Type::StructRef(name) | Type::EnumRef { name, .. } => serde_json::from_str(value)
144 .with_context(|| format!("`{value}` is not valid JSON for `{name}`"))?,
145 Type::StructTable { struct_ref } => serde_json::from_str(value)
146 .with_context(|| format!("`{value}` is not valid JSON for table `{struct_ref}`"))?,
147
148 other => {
151 bail!("unsupported parameter type {other:?} — endpoint-validator needs updating")
152 }
153 })
154 }
155}
156
157fn split_top_level(value: &str, sep: char) -> Vec<&str> {
162 let (mut parts, mut depth, mut start, mut quoted) = (Vec::new(), 0i32, 0usize, false);
163 for (i, c) in value.char_indices() {
164 match c {
165 '"' => quoted = !quoted,
166 '{' | '[' if !quoted => depth += 1,
167 '}' | ']' if !quoted => depth -= 1,
168 c if c == sep && depth == 0 && !quoted => {
169 parts.push(value[start..i].trim());
170 start = i + c.len_utf8();
171 }
172 _ => {}
173 }
174 }
175 parts.push(value[start..].trim());
176 parts.into_iter().filter(|p| !p.is_empty()).collect()
177}
178
179pub fn extract_param_defaults(
180 endpoints: &HashMap<String, EndpointData>,
181) -> HashMap<String, HashMap<String, String>> {
182 let mut result = HashMap::new();
183
184 for (method_id, endpoint_data) in endpoints {
185 let mut param_map = HashMap::new();
186 for (param_name, param_value) in &endpoint_data.params {
187 let value_str = match param_value {
188 ParamValue::String(s) => s.clone(),
189 ParamValue::Number(n) => n.to_string(),
190 ParamValue::Bool(b) => b.to_string(),
191 ParamValue::Array(arr) => format!("{:?}", arr),
192 ParamValue::Object(obj) => format!("{:?}", obj),
193 };
194 param_map.insert(param_name.to_string(), value_str);
195 }
196 result.insert(method_id.to_string(), param_map);
197 }
198
199 result
200}