datasources 0.1.21

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use datasources::{encode_rows, FromSchema, Row, Schema, SchemaObject, Value};
use std::collections::HashMap;
use uuid::Uuid;

#[derive(Clone, Debug, PartialEq, Default, datasources::Facet)]
#[facet(crate = ::datasources::facet)]
struct LocalePref {
    locale: String,
    timezone: String,
}

#[derive(Clone, Debug, PartialEq, Default, datasources::Facet)]
#[facet(crate = ::datasources::facet)]
struct GeneratedShape {
    uuid: String,
    created: datasources::chrono::DateTime<datasources::chrono::Utc>,
    version: Vec<u8>,
    contact_id: String,
    line2: Option<String>,
    settings: HashMap<String, String>,
    prefs: HashMap<String, LocalePref>,
}

impl SchemaObject for GeneratedShape {}

#[test]
fn generated_shape_tokens_compile() {
    let item = GeneratedShape {
        uuid: datasources::uuid::Uuid::new_v4().to_string(),
        created: datasources::chrono::Utc::now(),
        version: vec![0],
        contact_id: "1".into(),
        line2: None,
        settings: HashMap::new(),
        prefs: HashMap::new(),
    };
    assert!(!item.uuid.is_empty());
}

#[test]
fn from_schema_sets_string_from_uuid_and_datetime() {
    let id = Uuid::parse_str("54ef41f2-0cb4-4c72-97a7-776fd183f5ed").unwrap();
    let created = datasources::chrono::Utc::now();
    let mut row = datasources::Row::new();
    row.insert("uuid".into(), Value::Uuid(id));
    row.insert("created".into(), Value::DateTime(created));
    let schema = Schema::new(HashMap::from([
        ("uuid".into(), "uuid".into()),
        ("created".into(), "datetime".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.uuid, id.to_string());
    assert_eq!(item.created, created);
}

#[test]
fn from_schema_sets_datetime_from_iso_text() {
    let mut row = datasources::Row::new();
    row.insert("uuid".into(), Value::from("54ef41f2-0cb4-4c72-97a7-776fd183f5ed"));
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    let schema = Schema::new(HashMap::from([
        ("uuid".into(), "uuid".into()),
        ("created".into(), "datetime".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.created.to_rfc3339_opts(datasources::chrono::SecondsFormat::Millis, true), "2026-10-30T02:04:49.339Z");
}

#[test]
fn from_schema_sets_bytes_from_base64_text() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("version".into(), Value::from("AA=="));
    let schema = Schema::new(HashMap::from([
        ("created".into(), "datetime".into()),
        ("version".into(), "binary".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.version, vec![0]);
}

#[test]
fn from_schema_sets_i32_from_empty_text() {
    #[derive(Clone, Debug, PartialEq, Default, datasources::Facet)]
    #[facet(crate = ::datasources::facet)]
    struct WithId {
        id: i32,
    }
    impl SchemaObject for WithId {}
    let mut row = datasources::Row::new();
    row.insert("id".into(), Value::from(""));
    let schema = Schema::new(HashMap::from([("id".into(), "integer".into())]));
    let item = WithId::from_schema(row, &schema).unwrap();
    assert_eq!(item.id, 0);
}

#[test]
fn from_schema_sets_string_from_int() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("contact_id".into(), Value::Int(1));
    let schema = Schema::new(HashMap::from([
        ("created".into(), "datetime".into()),
        ("contact_id".into(), "integer".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.contact_id, "1");
}

#[test]
fn from_schema_sets_option_string_from_text() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("line2".into(), Value::from("flat 2"));
    let schema = Schema::new(HashMap::from([
        ("created".into(), "datetime".into()),
        ("line2".into(), "string".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.line2.as_deref(), Some("flat 2"));
}

#[test]
fn from_schema_sets_option_none_from_null() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("line2".into(), Value::Null);
    let schema = Schema::new(HashMap::from([
        ("created".into(), "datetime".into()),
        ("line2".into(), "string".into()),
    ]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.line2, None);
}

fn encoded(pairs: &[(&str, Value)]) -> Value {
    let mut row = Row::new();
    for (key, value) in pairs {
        row.insert((*key).into(), value.clone());
    }
    encode_rows(&[row])
}

#[test]
fn from_schema_sets_empty_map_from_empty_text() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("settings".into(), Value::from(""));
    let schema = Schema::new(HashMap::from([("created".into(), "datetime".into())]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert!(item.settings.is_empty());
}

#[derive(Clone, Debug, PartialEq, Default, datasources::Facet)]
#[facet(crate = ::datasources::facet)]
struct ChildShape {
    attributes: HashMap<String, String>,
}

#[derive(Clone, Debug, PartialEq, Default, datasources::Facet)]
#[facet(crate = ::datasources::facet)]
struct ParentShape {
    children: Vec<ChildShape>,
}

impl SchemaObject for ParentShape {}

#[test]
fn from_schema_sets_nested_empty_map_from_empty_text() {
    let mut row = datasources::Row::new();
    row.insert("children".into(), encoded(&[("attributes", Value::from(""))]));
    let item = ParentShape::from_schema(row, &Schema::new(HashMap::new())).unwrap();
    assert_eq!(item.children.len(), 1);
    assert!(item.children[0].attributes.is_empty());
}

#[test]
fn from_schema_sets_string_map_from_encoded_rows() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert("settings".into(), encoded(&[("key", Value::from("theme")), ("value", Value::from("dark"))]));
    let schema = Schema::new(HashMap::from([("created".into(), "datetime".into())]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(item.settings.get("theme").map(String::as_str), Some("dark"));
}

#[test]
fn from_schema_sets_struct_map_from_encoded_rows() {
    let mut row = datasources::Row::new();
    row.insert("created".into(), Value::from("2026-10-30T02:04:49.339Z"));
    row.insert(
        "prefs".into(),
        encoded(&[
            ("key", Value::from("home")),
            ("locale", Value::from("en-GB")),
            ("timezone", Value::from("Europe/London")),
        ]),
    );
    let schema = Schema::new(HashMap::from([("created".into(), "datetime".into())]));
    let item = GeneratedShape::from_schema(row, &schema).unwrap();
    let pref = item.prefs.get("home").expect("home");
    assert_eq!(pref.locale, "en-GB");
    assert_eq!(pref.timezone, "Europe/London");
}

#[test]
fn to_schema_encodes_maps() {
    let item = GeneratedShape {
        created: datasources::chrono::Utc::now(),
        settings: HashMap::from([("theme".into(), "dark".into())]),
        prefs: HashMap::from([(
            "home".into(),
            LocalePref {
                locale: "en-GB".into(),
                timezone: "Europe/London".into(),
            },
        )]),
        ..Default::default()
    };
    let schema = Schema::new(HashMap::new());
    let row = item.to_schema(&schema).unwrap();
    let back = GeneratedShape::from_schema(row, &schema).unwrap();
    assert_eq!(back.settings.get("theme").map(String::as_str), Some("dark"));
    assert_eq!(back.prefs.get("home").map(|pref| pref.locale.as_str()), Some("en-GB"));
}