capnweb-core 0.1.0

Core protocol implementation for Cap'n Web RPC - capability-based security with promise pipelining
Documentation
use crate::RpcError;
#[cfg(feature = "validation")]
use jsonschema;
use schemars::{schema_for, JsonSchema};
use serde_json::Value;

#[cfg(feature = "validation")]
pub struct Validator {
    schema: jsonschema::Validator,
}

#[cfg(feature = "validation")]
impl Validator {
    pub fn new(schema: Value) -> Result<Self, RpcError> {
        let compiled = jsonschema::validator_for(&schema)
            .map_err(|e| RpcError::bad_request(format!("Invalid schema: {}", e)))?;
        Ok(Validator { schema: compiled })
    }

    pub fn from_type<T: JsonSchema>() -> Result<Self, RpcError> {
        let schema = schema_for!(T);
        let schema_value = serde_json::to_value(schema)
            .map_err(|e| RpcError::internal(format!("Schema serialization error: {}", e)))?;
        Self::new(schema_value)
    }

    pub fn validate(&self, value: &Value) -> Result<(), Vec<String>> {
        if self.schema.is_valid(value) {
            Ok(())
        } else {
            Err(vec!["Validation failed".to_string()])
        }
    }

    pub fn is_valid(&self, value: &Value) -> bool {
        self.schema.is_valid(value)
    }
}

#[cfg(not(feature = "validation"))]
pub struct Validator;

#[cfg(not(feature = "validation"))]
impl Validator {
    pub fn new(_schema: Value) -> Result<Self, RpcError> {
        Err(RpcError::internal("Validation feature not enabled"))
    }

    pub fn validate(&self, _value: &Value) -> Result<(), Vec<String>> {
        Err(vec!["Validation feature not enabled".to_string()])
    }

    pub fn is_valid(&self, _value: &Value) -> bool {
        true
    }
}

#[cfg(all(test, feature = "validation"))]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_validator_creation() {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": { "type": "string" },
                "age": { "type": "number" }
            },
            "required": ["name"]
        });

        let validator = Validator::new(schema).unwrap();
        assert!(validator.is_valid(&json!({"name": "Alice", "age": 30})));
        assert!(validator.is_valid(&json!({"name": "Bob"})));
        assert!(!validator.is_valid(&json!({"age": 30})));
    }

    #[test]
    fn test_validation_errors() {
        let schema = json!({
            "type": "object",
            "properties": {
                "count": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                }
            }
        });

        let validator = Validator::new(schema).unwrap();

        let valid = json!({"count": 50});
        assert!(validator.validate(&valid).is_ok());

        let invalid = json!({"count": 150});
        let result = validator.validate(&invalid);
        assert!(result.is_err());
    }

    #[derive(JsonSchema)]
    #[allow(dead_code)]
    struct TestStruct {
        name: String,
        age: Option<u32>,
    }

    #[test]
    fn test_from_type() {
        let validator = Validator::from_type::<TestStruct>().unwrap();

        assert!(validator.is_valid(&json!({
            "name": "Alice",
            "age": 30
        })));

        assert!(validator.is_valid(&json!({
            "name": "Bob"
        })));

        assert!(!validator.is_valid(&json!({
            "age": 30
        })));
    }
}