rydantic 0.6.0

Type-driven validation, JSON Schema, Cap'n Proto, Axum and SQLx models for Rust
Documentation
#![cfg(all(feature = "axum", feature = "sqlite"))]
#[path = "../examples/sqlite_api.rs"]
#[allow(dead_code)]
mod api;
use rydantic::{sqlx::sqlite::SqlitePoolOptions, Model, SqlModel};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

async fn http(
    address: std::net::SocketAddr,
    method: &str,
    path: &str,
    body: &str,
) -> (u16, rydantic::Value) {
    let mut stream = tokio::net::TcpStream::connect(address).await.unwrap();
    stream.write_all(format!("{method} {path} HTTP/1.1\r\nHost: {address}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",body.len()).as_bytes()).await.unwrap();
    let mut bytes = Vec::new();
    stream.read_to_end(&mut bytes).await.unwrap();
    let text = std::str::from_utf8(&bytes).unwrap();
    let (headers, body) = text.split_once("\r\n\r\n").unwrap();
    let status = headers.split_whitespace().nth(1).unwrap().parse().unwrap();
    (
        status,
        if body.is_empty() {
            rydantic::Value::Null
        } else {
            serde_json::from_str(body).unwrap()
        },
    )
}
#[tokio::test]
async fn real_tcp_http_server_runs_validated_sqlite_crud_on_tokio() {
    let pool = SqlitePoolOptions::new()
        .max_connections(1)
        .connect("sqlite::memory:")
        .await
        .unwrap();
    api::User::create_table(&pool).await.unwrap();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let address = listener.local_addr().unwrap();
    let app = api::router(pool.clone());
    let server = tokio::spawn(async move {
        rydantic::axum::framework::serve(listener, app)
            .await
            .unwrap()
    });
    let run = async {
        let (status, body) = http(address, "POST", "/users", r#"{"name":"A","age":17}"#).await;
        assert_eq!(status, 422);
        assert_eq!(body["detail"].as_array().unwrap().len(), 2);
        assert!(api::User::select()
            .fetch_all(&pool)
            .await
            .unwrap()
            .is_empty());
        let (status, body) =
            http(address, "POST", "/users", r#"{"name":" Alice ","age":25}"#).await;
        assert_eq!(status, 201);
        assert_eq!(body, rydantic::json!({"id":1,"name":"Alice","age":25}));
        let (status, body) = http(address, "GET", "/users/1", "").await;
        assert_eq!(status, 200);
        assert_eq!(body["name"], "Alice");
        let (status, body) = http(address, "POST", "/users", r#"{"name":"Alice","age":25}"#).await;
        assert_eq!(status, 409);
        assert_eq!(body["detail"][0]["code"], "sql_constraint");
        assert_eq!(body["detail"][0]["loc"], rydantic::json!([]));
        assert_eq!(body["detail"][0]["stage"], "validate");
        assert!(!body.to_string().contains("UNIQUE"));
        let (status, body) = http(address, "PUT", "/users/1", r#"{"name":" Bob ","age":30}"#).await;
        assert_eq!(status, 200);
        assert_eq!(body["name"], "Bob");
        assert_eq!(api::User::get(&pool, &1).await.unwrap().unwrap().age, 30);
        let (status, body) = http(address, "GET", "/users", "").await;
        assert_eq!(status, 200);
        assert_eq!(body.as_array().unwrap().len(), 1);
        assert!(api::User::json_schema(rydantic::SchemaMode::Output).is_object());
        assert_eq!(http(address, "DELETE", "/users/1", "").await.0, 204);
        assert_eq!(http(address, "GET", "/users/1", "").await.0, 404);
    };
    let result = tokio::time::timeout(std::time::Duration::from_secs(15), run).await;
    server.abort();
    result.unwrap();
}