use proptest::prelude::*;
use crate::diagnostic::SpecLocation;
use crate::operation::HttpMethod;
use crate::types::{
AdditionalProperties, ArrayConstraints, ArrayType, IntKind, NamedType, ObjectConstraints,
ObjectType, PrimitiveConstraints, PrimitiveKind, PrimitiveType, Property, TypeDef, UnionKind,
UnionType, UnionVariant, NULL_ID,
};
use crate::value::{Value, ValueRef};
use crate::{ApiInfo, Ir};
pub fn ident() -> impl Strategy<Value = String> {
"[a-zA-Z][a-zA-Z0-9_]{0,15}".prop_map(|s| s.to_string())
}
pub fn value() -> impl Strategy<Value = Value> {
prop_oneof![
Just(Value::Null),
any::<bool>().prop_map(|b| Value::Bool { value: b }),
any::<i64>().prop_map(|v| Value::Int { value: v }),
proptest::num::f64::NORMAL.prop_map(|v| Value::Float { value: v }),
".{0,20}".prop_map(|s| Value::String { value: s }),
]
}
pub fn values_pool() -> impl Strategy<Value = Vec<Value>> {
(
prop::collection::vec(value(), 0..6),
any::<bool>(),
any::<bool>(),
)
.prop_map(|(scalars, with_list, with_object)| {
let mut pool = scalars;
if with_list && !pool.is_empty() {
let items: Vec<ValueRef> = (0..pool.len() as ValueRef).take(3).collect();
pool.push(Value::List { items });
}
if with_object && !pool.is_empty() {
let fields: Vec<(String, ValueRef)> = (0..pool.len() as ValueRef)
.take(2)
.enumerate()
.map(|(i, r)| (format!("k{i}"), r))
.collect();
pool.push(Value::Object { fields });
}
pool
})
}
pub fn primitive_kind() -> impl Strategy<Value = PrimitiveKind> {
prop_oneof![
Just(PrimitiveKind::String),
Just(PrimitiveKind::Integer),
Just(PrimitiveKind::Number),
Just(PrimitiveKind::Bool),
]
}
pub fn primitive_constraints() -> impl Strategy<Value = PrimitiveConstraints> {
(
prop::option::of(any::<u64>()),
prop::option::of(any::<u64>()),
prop::option::of(".{0,8}".prop_map(|s| s.to_string())),
)
.prop_map(|(min_len, max_len, pat)| PrimitiveConstraints {
minimum: None,
maximum: None,
exclusive_minimum: None,
exclusive_maximum: None,
multiple_of: None,
min_length: min_len,
max_length: max_len,
pattern: pat,
format_extension: None,
content_encoding: None,
content_media_type: None,
content_schema: None,
})
}
pub fn primitive_type() -> impl Strategy<Value = PrimitiveType> {
(primitive_kind(), primitive_constraints())
.prop_map(|(kind, constraints)| PrimitiveType { kind, constraints })
}
pub fn array_type() -> impl Strategy<Value = ArrayType> {
(
ident(),
prop::option::of(any::<u64>()),
prop::option::of(any::<u64>()),
any::<bool>(),
)
.prop_map(|(items, min, max, unique)| ArrayType {
items,
constraints: ArrayConstraints {
min_items: min,
max_items: max,
unique_items: unique,
},
})
}
pub fn additional_properties() -> impl Strategy<Value = AdditionalProperties> {
prop_oneof![
Just(AdditionalProperties::Forbidden),
Just(AdditionalProperties::Any),
ident().prop_map(|t| AdditionalProperties::Typed { r#type: t }),
]
}
pub fn property() -> impl Strategy<Value = Property> {
(
ident(),
ident(),
any::<bool>(),
any::<bool>(),
any::<bool>(),
any::<bool>(),
)
.prop_map(|(name, ty, required, deprecated, ro, wo)| Property {
name,
r#type: ty,
required,
documentation: None,
deprecated,
read_only: ro,
write_only: wo,
default: None,
extensions: vec![],
})
}
pub fn object_type() -> impl Strategy<Value = ObjectType> {
(
prop::collection::vec(property(), 0..4),
additional_properties(),
)
.prop_map(|(props, ap)| ObjectType {
properties: props,
additional_properties: ap,
constraints: ObjectConstraints::default(),
})
}
pub fn union_type() -> impl Strategy<Value = UnionType> {
(
prop::collection::vec(
(ident(), prop::option::of(ident()))
.prop_map(|(t, tag)| UnionVariant { r#type: t, tag }),
1..4,
),
prop_oneof![Just(UnionKind::OneOf), Just(UnionKind::AnyOf)],
)
.prop_map(|(mut variants, kind)| {
variants.sort_by_key(|v| (v.r#type == NULL_ID) as u8);
UnionType {
variants,
discriminator: None,
kind,
}
})
}
pub fn type_def() -> impl Strategy<Value = TypeDef> {
prop_oneof![
primitive_type().prop_map(TypeDef::Primitive),
array_type().prop_map(TypeDef::Array),
object_type().prop_map(TypeDef::Object),
union_type().prop_map(TypeDef::Union),
Just(TypeDef::Null),
]
}
pub fn named_type() -> impl Strategy<Value = NamedType> {
(ident(), type_def()).prop_map(|(id, definition)| NamedType {
id,
original_name: None,
documentation: None,
title: None,
read_only: false,
write_only: false,
external_docs: None,
default: None,
examples: vec![],
xml: None,
definition,
extensions: vec![],
location: None,
})
}
pub fn http_method() -> impl Strategy<Value = HttpMethod> {
prop_oneof![
Just(HttpMethod::Get),
Just(HttpMethod::Put),
Just(HttpMethod::Post),
Just(HttpMethod::Delete),
Just(HttpMethod::Options),
Just(HttpMethod::Head),
Just(HttpMethod::Patch),
Just(HttpMethod::Trace),
]
}
pub fn spec_location() -> impl Strategy<Value = SpecLocation> {
(".{0,20}".prop_map(|s| s.to_string()),).prop_map(|(p,)| SpecLocation::new(p))
}
pub fn int_kind() -> impl Strategy<Value = IntKind> {
prop_oneof![Just(IntKind::Int32), Just(IntKind::Int64)]
}
pub fn small_ir() -> impl Strategy<Value = Ir> {
(
ident(),
ident(),
prop::collection::vec(named_type(), 0..6),
values_pool(),
)
.prop_map(|(title, version, types, values)| Ir {
info: ApiInfo {
title,
version,
description: None,
summary: None,
terms_of_service: None,
contact: None,
license_name: None,
license_url: None,
license_identifier: None,
extensions: vec![],
},
operations: vec![],
types,
security_schemes: vec![],
servers: vec![],
webhooks: vec![],
external_docs: None,
tags: vec![],
json_schema_dialect: None,
self_url: None,
values,
})
}
#[cfg(test)]
mod tests {
use super::*;
proptest! {
#[test]
fn small_ir_serializes(ir in small_ir()) {
let s = serde_json::to_string(&ir).unwrap();
let _back: Ir = serde_json::from_str(&s).unwrap();
}
}
}