Skip to content

Commit

Permalink
🚧 Continue working on advanced types
Browse files Browse the repository at this point in the history
  • Loading branch information
rster2002 committed Oct 28, 2023
1 parent 64dd723 commit 3899369
Show file tree
Hide file tree
Showing 6 changed files with 400 additions and 24 deletions.
137 changes: 129 additions & 8 deletions src/schema_type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
Expand All @@ -9,23 +10,48 @@ use crate::traits::validator::Validator;
pub mod basic_type;
pub mod advanced_type;

#[derive(Debug, Serialize, Deserialize)]
/// Root schema type that encompasses all the different types that can be validated.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
#[cfg_attr(test, derive(PartialEq))]
pub enum SchemaType {
Basic(BasicType),
Advanced(AdvancedType),
Array(Vec<SchemaType>),
Array((Box<SchemaType>,)),
Tuple(Vec<SchemaType>),
Object(HashMap<String, SchemaType>),
}

impl Display for SchemaType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SchemaType::Basic(basic_type) => Display::fmt(basic_type, f),
SchemaType::Advanced(advanced_type) => Display::fmt(advanced_type, f),
SchemaType::Array(item) => {
write!(f, "array filled with '{}'", item.0.to_string())
}
SchemaType::Tuple(_) => {
todo!()
}
SchemaType::Object(_) => {
todo!()
}
}
}
}

#[derive(Debug, Error, PartialEq)]
pub enum SchemaTypeValidationError {
#[error("{0}")]
BasicTypeValidationError(#[from] BasicTypeValidationError),

#[error("{0}")]
AdvancedTypeValidationError(#[from] AdvancedTypeValidationError),

#[error("Expected an object, but got something else")]
NotAnObject,

#[error("Missing object key: '{0}'")]
MissingObjectKey(String),
}

impl Validator for SchemaType {
Expand All @@ -35,12 +61,31 @@ impl Validator for SchemaType {
match self {
SchemaType::Basic(basic_type) => Ok(basic_type.validate(value)?),
SchemaType::Advanced(advanced_type) => Ok(advanced_type.validate(value)?),
SchemaType::Array(_) => {
SchemaType::Array(items) => {
todo!()
}
SchemaType::Object(_) => {
SchemaType::Tuple(items) => {
todo!()
}
SchemaType::Object(map) => {
let Value::Object(target_map) = value else {
return Err(SchemaTypeValidationError::NotAnObject);
};

for (key, schema) in map {
let Some(value) = target_map.get(key) else {
if let SchemaType::Advanced(AdvancedType::Optional(_)) = schema {
return Ok(());
};

return Err(SchemaTypeValidationError::MissingObjectKey(key.to_string()));
};

schema.validate(value)?;
}

Ok(())
}
}
}
}
Expand All @@ -49,8 +94,10 @@ impl Validator for SchemaType {
mod tests {
use std::collections::HashMap;
use serde_json::json;
use crate::schema_type::{AdvancedType, BasicType, SchemaType};
use crate::schema_type::{AdvancedType, BasicType, SchemaType, SchemaTypeValidationError};
use crate::schema_type::advanced_type::advanced_string_type::AdvancedStringType;
use crate::schema_type::basic_type::BasicTypeValidationError;
use crate::traits::validator::Validator;

#[test]
fn basic_schema_type_can_be_deserialized() {
Expand Down Expand Up @@ -93,8 +140,82 @@ mod tests {
]))
.unwrap();

assert_eq!(value, SchemaType::Array(vec![
SchemaType::Basic(BasicType::String)
assert_eq!(value, SchemaType::Array((
Box::new(SchemaType::Basic(BasicType::String)),
)));
}

#[test]
fn nested_values_in_tuple_are_deserialized_correctly() {
let value: SchemaType = serde_json::from_value(json!([
"string",
"number"
]))
.unwrap();

assert_eq!(value, SchemaType::Tuple(vec![
SchemaType::Basic(BasicType::String),
SchemaType::Basic(BasicType::Number),
]));
}

#[test]
fn objects_are_validated_correctly() {
let value: SchemaType = serde_json::from_value(json!({
"name": "string",
"age": "number",
}))
.unwrap();

assert_eq!(value.validate(&json!({
"name": "Alice",
"age": 42
})), Ok(()));

assert_eq!(value.validate(&json!("")), Err(SchemaTypeValidationError::NotAnObject));

assert_eq!(value.validate(&json!({
"age": 42
})), Err(SchemaTypeValidationError::MissingObjectKey("name".to_string())));

assert_eq!(value.validate(&json!({
"name": 10,
"age": 42
})), Err(
SchemaTypeValidationError::BasicTypeValidationError(
BasicTypeValidationError::IncorrectType(
BasicType::String,
json!(10)
)
)
));
}

#[test]
fn optional_type_in_object_is_resolved_correctly() {
let advanced_type: SchemaType = serde_json::from_value(json!({
"name": {
"$": "optional",
"type": "string"
}
}))
.unwrap();

assert_eq!(advanced_type.validate(&json!({})), Ok(()));
}

#[test]
fn incorrect_optional_type_in_object_returns_an_error() {
let advanced_type: SchemaType = serde_json::from_value(json!({
"name": {
"$": "optional",
"type": "string"
}
}))
.unwrap();

assert!(advanced_type.validate(&json!({
"name": 10,
})).is_err());
}
}
109 changes: 98 additions & 11 deletions src/schema_type/advanced_type.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,62 @@
pub mod advanced_string_type;
pub mod any_of_type;
pub mod optional_type;

use std::fmt::{Display, Formatter, Pointer};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
use crate::schema_type::advanced_type::advanced_string_type::{AdvancedStringType, StringValidationError};
use crate::schema_type::SchemaType;
use crate::schema_type::{SchemaType, SchemaTypeValidationError};
use crate::schema_type::advanced_type::any_of_type::{AnyOfType, AnyOfTypeError};
use crate::schema_type::advanced_type::optional_type::OptionalType;
use crate::traits::validator::Validator;

#[derive(Debug, Serialize, Deserialize)]
/// Types that require more configuration than just checking if the type matches.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "$", rename_all = "camelCase")]
#[cfg_attr(test, derive(PartialEq))]
pub enum AdvancedType {
String(AdvancedStringType),
Optional(Box<SchemaType>),
AnyOf(AnyOfType),
FixedArray,
VariableArray,
Optional(OptionalType),
}

impl Display for AdvancedType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AdvancedType::String(advanced_string_type) => advanced_string_type.fmt(f),
AdvancedType::AnyOf(advanced_enum_type) => advanced_enum_type.fmt(f),
AdvancedType::FixedArray => {
todo!()
}
AdvancedType::VariableArray => {
todo!()
}
AdvancedType::Optional(_) => {
todo!()
}
}
}
}

#[derive(Debug, PartialEq, Error)]
pub enum AdvancedTypeValidationError {
#[error("{0}")]
StringValidationError(#[from] StringValidationError),

#[error("{0}")]
AnyOfError(#[from] AnyOfTypeError),

#[error("{0}")]
SchemaTypeValidationError(Box<SchemaTypeValidationError>),
}

impl From<SchemaTypeValidationError> for AdvancedTypeValidationError {
fn from(value: SchemaTypeValidationError) -> Self {
AdvancedTypeValidationError::SchemaTypeValidationError(Box::new(value))
}
}

impl Validator for AdvancedType {
Expand All @@ -27,11 +65,12 @@ impl Validator for AdvancedType {
fn validate(&self, value: &Value) -> Result<(), Self::E> {
match self {
AdvancedType::String(advanced_string) => Ok(advanced_string.validate(value)?),
AdvancedType::Optional(nested_type) => {
if let Value::Null = value {
return Ok(())
}

AdvancedType::Optional(optional_type) => Ok(optional_type.validate(value)?),
AdvancedType::AnyOf(advanced_enum) => Ok(advanced_enum.validate(value)?),
AdvancedType::FixedArray => {
todo!()
}
AdvancedType::VariableArray => {
todo!()
}
}
Expand All @@ -40,12 +79,60 @@ impl Validator for AdvancedType {

#[cfg(test)]
mod tests {
use serde_json::json;
use crate::schema_type::advanced_type::advanced_string_type::AdvancedStringType;
use crate::schema_type::advanced_type::AdvancedType;
use crate::schema_type::advanced_type::any_of_type::AnyOfType;
use crate::schema_type::advanced_type::optional_type::OptionalType;
use crate::schema_type::basic_type::BasicType;
use crate::schema_type::SchemaType;

#[test]
fn optional_advanced_type_is_resolved_correctly() {
let advanced_type = AdvancedType::Optional(Box::new(SchemaType::Basic(BasicType::String)));
fn advanced_string_type_is_deserialized_correctly() {
let advanced_type: AdvancedType = serde_json::from_value(json!({
"$": "string",
"requireFilled": false,
"minLength": 10,
"maxLength": 20,
}))
.unwrap();

assert_eq!(advanced_type, AdvancedType::String(AdvancedStringType {
require_filled: false,
min_length: Some(10),
max_length: Some(20),
}));
}

#[test]
fn any_of_type_is_deserialized_correctly() {
let advanced_type: AdvancedType = serde_json::from_value(json!({
"$": "anyOf",
"variants": [
"string",
"number",
],
}))
.unwrap();

assert_eq!(advanced_type, AdvancedType::AnyOf(AnyOfType {
variants: vec![
SchemaType::Basic(BasicType::String),
SchemaType::Basic(BasicType::Number),
],
}));
}

#[test]
fn optional_type_is_deserialized_correctly() {
let advanced_type: AdvancedType = serde_json::from_value(json!({
"$": "optional",
"type": "string"
}))
.unwrap();

assert_eq!(advanced_type, AdvancedType::Optional(OptionalType {
kind: Box::new(SchemaType::Basic(BasicType::String))
}));
}
}
14 changes: 12 additions & 2 deletions src/schema_type/advanced_type/advanced_string_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
Expand All @@ -9,16 +10,25 @@ fn default_true() -> bool {
true
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(test, derive(PartialEq))]
pub struct AdvancedStringType {
#[serde(default = "default_true")]
pub require_filled: bool,
pub min_length: Option<usize>,
pub max_length: Option<usize>,
}

impl Display for AdvancedStringType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.require_filled {
write!(f, "filled ")?;
}

write!(f, "string")
}
}

impl Default for AdvancedStringType {
fn default() -> Self {
Self {
Expand Down
Loading

0 comments on commit 3899369

Please sign in to comment.