use crate::common::runtime_expression;
use crate::validation::{Context, ValidateWithContext};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Parameter {
#[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
pub enum_values: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(flatten)]
#[serde(with = "crate::common::extensions")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
impl ValidateWithContext for Parameter {
fn validate_with_context(&self, ctx: &mut Context) {
if let Some(location) = &self.location {
if location.is_empty() {
ctx.error_field("location", "must not be empty");
} else if let Err(err) = runtime_expression::parse(location) {
ctx.error_field(
"location",
format!(
"`{location}` is not a valid runtime expression: {}",
err.message()
),
);
}
}
if let Some(default) = &self.default
&& !self.enum_values.is_empty()
&& !self.enum_values.contains(default)
{
ctx.error_field(
"default",
format!("`{default}` is not one of the values listed in `enum`"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use enumset::EnumSet;
use serde_json::json;
#[test]
fn round_trips_a_full_parameter() {
let value = json!({
"enum": ["1", "2"],
"default": "1",
"description": "Street id",
"examples": ["1"],
"location": "$message.payload#/streetId"
});
let parameter: Parameter = serde_json::from_value(value.clone()).unwrap();
assert_eq!(serde_json::to_value(¶meter).unwrap(), value);
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.streetId");
parameter.validate_with_context(&mut ctx);
assert!(ctx.errors.is_empty(), "got: {:?}", ctx.errors);
}
#[test]
fn empty_parameter_is_valid() {
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.streetId");
Parameter::default().validate_with_context(&mut ctx);
assert!(ctx.errors.is_empty());
}
#[test]
fn rejects_malformed_and_empty_location() {
let bad: Parameter = serde_json::from_value(json!({ "location": "payload#/id" })).unwrap();
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.p");
bad.validate_with_context(&mut ctx);
assert!(
ctx.errors
.iter()
.any(|e| e.contains("is not a valid runtime expression"))
);
let empty = Parameter {
location: Some(String::new()),
..Default::default()
};
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.p");
empty.validate_with_context(&mut ctx);
assert!(ctx.errors[0] == "#.parameters.p.location: must not be empty");
}
#[test]
fn default_must_be_one_of_the_enumerated_values() {
let parameter: Parameter =
serde_json::from_value(json!({ "enum": ["a", "b"], "default": "c" })).unwrap();
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.p");
parameter.validate_with_context(&mut ctx);
assert!(
ctx.errors
.iter()
.any(|e| e.contains("is not one of the values listed in `enum`")),
"got: {:?}",
ctx.errors
);
let free: Parameter = serde_json::from_value(json!({ "default": "c" })).unwrap();
let mut ctx = Context::with_path(EnumSet::empty(), "#.parameters.p");
free.validate_with_context(&mut ctx);
assert!(ctx.errors.is_empty());
}
}