kdb-connection 0.1.0

kDB connection.
Documentation
use serde::Deserialize;
use serde::de::Deserializer;
use serde::de::Error as _;
use serde_json::Value as JsonValue;

use crate::ValueArray;
use crate::ValueHash;
use crate::errors::Error;

use super::{Value, ValueData};

impl<'de> Deserialize<'de> for ValueData
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let json = serde_json::Value::deserialize(deserializer)?;
        let value = lift_json(json).map_err(D::Error::custom)?;
        Ok(value.value)
    }
}

fn is_typed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool
{
    (map.len() == 2
        || (map.len() == 3
            && map.get("type").is_some_and(|x| match x
            {
                serde_json::Value::String(string) => string == "literal",
                _ => false,
            })))
        && map
            .get("datatype")
            .is_some_and(|x| matches!(x, serde_json::Value::String(_)))
        && map.contains_key("value")
}

fn lift_json(j: JsonValue) -> Result<Value, crate::Error>
{
    Ok(match j
    {
        JsonValue::Null => ValueData::Null.into(),

        JsonValue::Bool(b) => b.into(),

        JsonValue::Number(n) =>
        {
            if let Some(i) = n.as_i64()
            {
                ValueData::Integer64(i).into()
            }
            else if let Some(f) = n.as_f64()
            {
                ValueData::Float64(f).into()
            }
            else
            {
                return Err(Error::UnsupportedNumber);
            }
        }

        JsonValue::String(s) => s.into(),

        JsonValue::Array(arr) =>
        {
            let values: ValueArray = arr.into_iter().map(lift_json).collect::<Result<_, _>>()?;

            values.into()
        }

        JsonValue::Object(map) =>
        {
            // 1. Already a typed Value → preserve exactly
            if is_typed_value(&map)
            {
                return serde_json::from_value(JsonValue::Object(map)).map_err(|e| e.into());
            }

            // 2. Raw object → valuehash
            let values: ValueHash = map
                .into_iter()
                .map(|(k, v)| lift_json(v).map(|val| (k, val)))
                .collect::<Result<_, _>>()?;

            values.into()
        }
    })
}

#[derive(Deserialize)]
struct RawValue
{
    #[serde(default)]
    datatype: Option<String>,
    value: ValueData,
}

fn guess_datatype(value: &ValueData) -> &'static str
{
    match value
    {
        ValueData::Null => "http://www.w3.org/2001/XMLSchema#nil",
        ValueData::Boolean(_) => "http://www.w3.org/2001/XMLSchema#bool",
        ValueData::Integer64(_) => "http://www.w3.org/2001/XMLSchema#long",
        ValueData::Float64(_) => "http://www.w3.org/2001/XMLSchema#float64",
        ValueData::String(_) => "http://www.w3.org/2001/XMLSchema#string",
        ValueData::Map(_) => "http://askco.re/datatype#valuehash",
        ValueData::Array(_) => "http://askco.re/datatype#valuelist",
    }
}

impl<'de> Deserialize<'de> for Value
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = RawValue::deserialize(deserializer)?;

        let datatype = match raw.datatype
        {
            Some(dt) => dt,
            None => guess_datatype(&raw.value).to_string(),
        };

        Ok(Value {
            datatype,
            value: raw.value,
        })
    }
}

#[cfg(test)]
mod tests
{
    use super::*;
    use crate::{ValueArray, ValueHash, test::validate_dataset_0};

    #[test]
    fn test_agent_deserialization()
    {
        let raw_json = serde_json::json!({
            "datatype": "http://askco.re/datatype#valuelist",
            "type": "literal",
            "value": [
                {
                    "datatype": "http://askco.re/datatype#valuehash",
                    "value": {
                        "object_uri": {
                            "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                            "value": "http://example.org/agent/0"
                        },
                        "properties": {
                            "datatype": "http://askco.re/datatype#valuehash",
                            "value": {
                                "http://askco.re/agent#agent_type": {
                                    "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                                    "value": "http://askco.re/agent#uav"
                                },
                                "http://askco.re/agent#frame": {
                                    "datatype": "http://www.w3.org/2001/XMLSchema#string",
                                    "value": "agent0"
                                },
                                "http://xmlns.com/foaf/0.1/name": {
                                    "datatype": "http://www.w3.org/2001/XMLSchema#string",
                                    "value": "test agent"
                                }
                            }
                        },
                        "type_uri": {
                            "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                            "value": "http://askco.re/agent#uav"
                        }
                    }
                }
            ]
        });
        // Parse JSON into Value
        let agents: Value = serde_json::from_value(raw_json).unwrap();
        let agents: ValueArray = agents.to_owned().try_into().unwrap();
        let agent_0: ValueHash = agents.into_iter().next().unwrap().try_into().unwrap();

        crate::test::validate_agent_0(agent_0);
    }

    #[test]
    fn test_dataset_deserialization()
    {
        // JSON input (simplified, matching your examples)
        let raw_json = serde_json::json!({
            "datatype": "http://askco.re/datatype#valuehash",
            "value": {
                "object_uri": {
                    "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                    "value": "http://example.org/dataset/0"
                },
                "type_uri": {
                    "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                    "value": "http://askco.re/dataset#point_cloud_dataset"
                },
                "properties": {
                    "datatype": "http://askco.re/datatype#valuehash",
                    "value": {
                        "http://askco.re/dataset#content_type": {
                            "datatype": "http://www.w3.org/2001/XMLSchema#anyURI",
                            "value": "http://askco.re/sensing#point_cloud"
                        },
                        "http://askco.re/sensing#point_density": {
                            "datatype": "http://askco.re/datatype#quantityDecimal",
                            "value": {
                                "value": 10,
                                "unit": "point/m^2"
                            }
                        },
                        "http://www.opengis.net/ont/geosparql#hasGeometry": {
                            "datatype": "http://www.opengis.net/ont/geosparql#Geometry",
                            "value": {
                                "type": "Polygon",
                                "coordinates": [
                                    [
                                        [30, 10],
                                        [40, 40],
                                        [20, 40],
                                        [10, 20],
                                        [30, 10]
                                    ]
                                ]
                            }
                        }
                    }
                }
            }
        });

        // Parse JSON into Value
        let dataset_0: Value = serde_json::from_value(raw_json).unwrap();
        let dataset_0: ValueHash = dataset_0.to_owned().try_into().unwrap();

        validate_dataset_0(dataset_0);
    }
}