use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
num::NonZero,
ops::Range,
};
use poem::{Result, http::StatusCode, test::TestClient};
use poem_openapi::{
Multipart, Object, OpenApi, OpenApiService, Validator,
param::Query,
payload::Payload,
registry::{MetaApi, MetaSchema, Registry},
types::{ParseFromJSON, Type, multipart::JsonField},
validation,
validation::ValidatorMeta,
};
use serde_json::json;
#[test]
fn test_u64() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
n: u64,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 1 }))).unwrap(),
A { n: 1 }
);
}
#[test]
fn test_non_zero_u64() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
n: NonZero<u64>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 1 }))).unwrap(),
A {
n: NonZero::new(1).unwrap()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 0 })))
.unwrap_err()
.into_message(),
"failed to parse \"non_zero_integer(uint64)\": Integer should not be 0. (occurred while parsing \"A\")"
);
}
#[test]
fn test_multiple_of() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(multiple_of = "10"))]
n: i32,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 20 }))).unwrap(),
A { n: 20 }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 25 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. multipleOf(10)"
);
let mut schema = MetaSchema::new("string");
validation::MultipleOf::new(10.0).update_meta(&mut schema);
assert_eq!(schema.multiple_of, Some(10.0));
}
#[test]
fn test_maximum() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(maximum(value = "500")))]
n: i32,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 400 }))).unwrap(),
A { n: 400 }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 500 }))).unwrap(),
A { n: 500 }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 530 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. maximum(500, exclusive: false)"
);
let mut schema = MetaSchema::new("string");
validation::Maximum::new(10.0, false).update_meta(&mut schema);
assert_eq!(schema.maximum, Some(10.0));
assert_eq!(schema.exclusive_maximum, None);
}
#[test]
fn test_maximum_exclusive() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(maximum(value = "500", exclusive)))]
n: i32,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 400 }))).unwrap(),
A { n: 400 }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 500 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. maximum(500, exclusive: true)"
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 530 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. maximum(500, exclusive: true)"
);
let mut schema = MetaSchema::new("string");
validation::Maximum::new(10.0, true).update_meta(&mut schema);
assert_eq!(schema.maximum, Some(10.0));
assert_eq!(schema.exclusive_maximum, Some(true));
}
#[test]
fn test_max_length() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(max_length = "5"))]
value: String,
}
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcd" }))).unwrap(),
A {
value: "abcd".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcde" }))).unwrap(),
A {
value: "abcde".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "שלום!" }))).unwrap(),
A {
value: "שלום!".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcdef" })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `value` verification failed. maxLength(5)"
);
let mut schema = MetaSchema::new("string");
validation::MaxLength::new(10).update_meta(&mut schema);
assert_eq!(schema.max_length, Some(10));
}
#[test]
fn test_min_length() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(min_length = "5"))]
value: String,
}
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcde" }))).unwrap(),
A {
value: "abcde".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcdef" }))).unwrap(),
A {
value: "abcdef".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "abcd" })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `value` verification failed. minLength(5)"
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "שלום" })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `value` verification failed. minLength(5)"
);
let mut schema = MetaSchema::new("string");
validation::MinLength::new(10).update_meta(&mut schema);
assert_eq!(schema.min_length, Some(10));
}
#[test]
fn test_pattern() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(pattern = r#"\[.*\]"#))]
value: String,
}
assert_eq!(
A::parse_from_json(Some(json!({ "value": "[123]" }))).unwrap(),
A {
value: "[123]".to_string()
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "value": "123" })))
.unwrap_err()
.into_message(),
r#"failed to parse "A": field `value` verification failed. pattern("\[.*\]")"#
);
let mut schema = MetaSchema::new("string");
validation::Pattern::new(r"\[.*\]").update_meta(&mut schema);
assert_eq!(schema.pattern.as_deref(), Some(r"\[.*\]"));
}
#[test]
fn test_max_items() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(max_items = "3"))]
values: Vec<String>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "3"] }))).unwrap(),
A {
values: vec!["1".to_string(), "2".to_string(), "3".to_string()],
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "3", "4"] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. maxItems(3)"
);
let mut schema = MetaSchema::new("string");
validation::MaxItems::new(10).update_meta(&mut schema);
assert_eq!(schema.max_items, Some(10));
}
#[test]
fn test_min_items() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(min_items = "4"))]
values: Vec<String>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "3", "4"] }))).unwrap(),
A {
values: vec![
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string()
],
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "3"] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. minItems(4)"
);
let mut schema = MetaSchema::new("string");
validation::MinItems::new(10).update_meta(&mut schema);
assert_eq!(schema.min_items, Some(10));
}
#[test]
fn test_unique_items() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(unique_items))]
values: Vec<String>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "3"] }))).unwrap(),
A {
values: vec!["1".to_string(), "2".to_string(), "3".to_string(),],
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["1", "2", "2"] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. uniqueItems()"
);
let mut schema = MetaSchema::new("string");
validation::UniqueItems.update_meta(&mut schema);
assert_eq!(schema.unique_items, Some(true));
}
#[tokio::test]
async fn param_validator() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get")]
async fn test1(
&self,
#[oai(name = "v", validator(maximum(value = "100", exclusive)))] _v: Query<i32>,
) {
}
#[oai(path = "/test2", method = "get")]
async fn test2(
&self,
#[oai(name = "v", validator(maximum(value = "100", exclusive)))] _v: Query<i32>,
) -> Result<()> {
Ok(())
}
}
let api = OpenApiService::new(Api, "test1", "1.0");
let cli = TestClient::new(api);
let resp = cli.get("/").query("v", &999).send().await;
resp.assert_status(StatusCode::BAD_REQUEST);
resp.assert_text(
"failed to parse parameter `v`: verification failed. maximum(100, exclusive: true)",
)
.await;
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(
meta.paths[0].operations[0].params[0]
.schema
.unwrap_inline()
.maximum,
Some(100.0)
);
assert_eq!(
meta.paths[0].operations[0].params[0]
.schema
.unwrap_inline()
.exclusive_maximum,
Some(true)
);
cli.get("/")
.query("v", &50)
.send()
.await
.assert_status_is_ok();
let resp = cli.get("/test2").query("v", &101).send().await;
resp.assert_status(StatusCode::BAD_REQUEST);
resp.assert_text(
"failed to parse parameter `v`: verification failed. maximum(100, exclusive: true)",
)
.await;
cli.get("/test2")
.query("v", &50)
.send()
.await
.assert_status_is_ok();
}
#[test]
fn test_option() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(multiple_of = "10"))]
n1: Option<i32>,
#[oai(validator(multiple_of = "10"))]
n2: Option<Option<i32>>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n1": 20 }))).unwrap(),
A {
n1: Some(20),
n2: None
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "n2": 20 }))).unwrap(),
A {
n1: None,
n2: Some(Some(20)),
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "n2": null }))).unwrap(),
A { n1: None, n2: None }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n1": 25 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n1` verification failed. multipleOf(10)"
);
assert_eq!(
A::parse_from_json(Some(json!({ "n2": 25 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n2` verification failed. multipleOf(10)"
);
}
#[test]
fn test_multiple_validators() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(multiple_of = "10", maximum(value = "500")))]
n: i32,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": 20 }))).unwrap(),
A { n: 20 }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 25 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. multipleOf(10)"
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": 530 })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. maximum(500, exclusive: false)"
);
}
#[test]
fn test_unsigned_integers() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
u8: u8,
u16: u16,
u32: u32,
u64: u64,
}
assert_eq!(
A::parse_from_json(Some(json!({
"u8": u8::MAX as u64,
"u16": u16::MAX as u64,
"u32": u32::MAX as u64,
"u64": u64::MAX,
})))
.unwrap(),
A {
u8: u8::MAX,
u16: u16::MAX,
u32: u32::MAX,
u64: u64::MAX,
}
);
assert_eq!(
A::parse_from_json(Some(json!({
"u8": u8::MAX as u64 + 1,
"u16": u16::MAX as u64,
"u32": u32::MAX as u64,
"u64": u64::MAX,
})))
.unwrap_err()
.into_message(),
"failed to parse \"integer(uint8)\": Only integers from 0 to 255 are accepted. (occurred while parsing \"A\")"
);
}
#[test]
fn test_list_on_object() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(maximum(value = "10")))]
n: Vec<i32>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "n": [1, 2, 3] }))).unwrap(),
A { n: vec![1, 2, 3] }
);
assert_eq!(
A::parse_from_json(Some(json!({ "n": [1, 2, 3, 25] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `n` verification failed. maximum(10, exclusive: false)"
);
let mut registry = Registry::default();
A::register(&mut registry);
let schema = registry.schemas.get_mut("A").unwrap();
let (name, field_n) = schema.properties.remove(0);
assert_eq!(name, "n");
let schema_n = field_n.unwrap_inline();
let schema_items = schema_n.items.as_ref().unwrap();
let schema_items = schema_items.unwrap_inline();
assert_eq!(schema_items.maximum, Some(10.0));
}
#[test]
fn test_list_on_multipart() {
#[derive(Multipart, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(maximum(value = "32")))]
values: Vec<JsonField<i32>>,
}
let schema_ref = A::schema_ref();
let schema: &MetaSchema = schema_ref.unwrap_inline();
assert_eq!(schema.ty, "object");
assert_eq!(schema.properties.len(), 1);
assert_eq!(schema.properties[0].0, "values");
let schema_values = schema.properties[0].1.unwrap_inline();
assert_eq!(schema_values.ty, "array");
let schema_items = schema_values.items.as_ref().unwrap();
let schema_items = schema_items.unwrap_inline();
assert_eq!(schema_items.maximum, Some(32.0));
}
#[test]
fn test_both_max_items_and_max_length() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(max_items = 2, max_length = 3))]
values: Vec<String>,
}
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["a", "b"] }))).unwrap(),
A {
values: vec!["a".to_string(), "b".to_string()]
}
);
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["a", "b", "c"] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. maxItems(2)"
);
assert_eq!(
A::parse_from_json(Some(json!({ "values": ["a", "bcde"] })))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. maxLength(3)"
);
}
#[test]
fn test_max_properties() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(max_properties = 3))]
values: HashMap<String, u32>,
}
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
"b": 2,
"c": 3,
},
})))
.unwrap(),
A {
values: {
let mut map = HashMap::new();
map.insert("a".to_string(), 1);
map.insert("b".to_string(), 2);
map.insert("c".to_string(), 3);
map
},
}
);
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
},
})))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. maxProperties(3)"
);
let mut schema = MetaSchema::new("string");
validation::MaxProperties::new(3).update_meta(&mut schema);
assert_eq!(schema.max_properties, Some(3));
}
#[test]
fn test_min_properties() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(min_properties = 2))]
values: HashMap<String, u32>,
}
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
"b": 2,
"c": 3,
},
})))
.unwrap(),
A {
values: {
let mut map = HashMap::new();
map.insert("a".to_string(), 1);
map.insert("b".to_string(), 2);
map.insert("c".to_string(), 3);
map
},
}
);
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
},
})))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. minProperties(2)"
);
let mut schema = MetaSchema::new("string");
validation::MinProperties::new(3).update_meta(&mut schema);
assert_eq!(schema.min_properties, Some(3));
}
#[test]
fn test_map_on_object() {
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(maximum(value = "100")))]
values: HashMap<String, u32>,
}
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
"b": 2,
"c": 3,
},
})))
.unwrap(),
A {
values: {
let mut map = HashMap::new();
map.insert("a".to_string(), 1);
map.insert("b".to_string(), 2);
map.insert("c".to_string(), 3);
map
},
}
);
assert_eq!(
A::parse_from_json(Some(json!({
"values": {
"a": 1,
"b": 101,
},
})))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `values` verification failed. maximum(100, exclusive: false)"
);
let mut registry = Registry::default();
A::register(&mut registry);
let schema = registry.schemas.get_mut("A").unwrap();
let (name, field_values) = schema.properties.remove(0);
assert_eq!(name, "values");
let schema_values = field_values.unwrap_inline();
let schema_properties = schema_values.additional_properties.as_ref().unwrap();
let schema_properties = schema_properties.unwrap_inline();
assert_eq!(schema_properties.maximum, Some(100.0));
}
#[test]
fn test_custom_validator() {
struct MyIntValidator(Range<i32>);
impl Display for MyIntValidator {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("MyIntValidator")
}
}
impl Validator<i32> for MyIntValidator {
fn check(&self, value: &i32) -> bool {
self.0.contains(value)
}
}
#[derive(Object, Debug, Eq, PartialEq)]
struct A {
#[oai(validator(custom = "MyIntValidator(100..200)"))]
value: i32,
}
assert_eq!(
A::parse_from_json(Some(json!({
"value": 150,
})))
.unwrap(),
A { value: 150 }
);
assert_eq!(
A::parse_from_json(Some(json!({
"value": 200,
})))
.unwrap_err()
.into_message(),
"failed to parse \"A\": field `value` verification failed. MyIntValidator"
);
}