attribute and json
parent
c364e9ae54
commit
26d1718af1
@ -0,0 +1,252 @@
|
||||
use crate::data::encode::Encoded;
|
||||
use crate::data::id::{AttrId, EntityId, TxId};
|
||||
use crate::data::keyword::Keyword;
|
||||
use crate::data::value::Value;
|
||||
use anyhow::Result;
|
||||
use rmp_serde::Serializer;
|
||||
use serde::Serialize;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AttributeError {
|
||||
#[error("cannot convert {0} to {1}")]
|
||||
Conversion(String, String),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, PartialEq, Ord, PartialOrd, Eq, Debug, Deserialize, Serialize)]
|
||||
pub(crate) enum AttributeCardinality {
|
||||
One = 1,
|
||||
Many = 2,
|
||||
}
|
||||
|
||||
impl Display for AttributeCardinality {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AttributeCardinality::One => write!(f, "one"),
|
||||
AttributeCardinality::Many => write!(f, "many"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for AttributeCardinality {
|
||||
type Error = AttributeError;
|
||||
fn try_from(value: &'_ str) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
"one" => Ok(AttributeCardinality::One),
|
||||
"many" => Ok(AttributeCardinality::Many),
|
||||
s => Err(AttributeError::Conversion(
|
||||
s.to_string(),
|
||||
"AttributeCardinality".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug, Deserialize, Serialize)]
|
||||
pub(crate) enum AttributeTyping {
|
||||
Ref = 1,
|
||||
Component = 2,
|
||||
Bool = 3,
|
||||
Int = 4,
|
||||
Float = 5,
|
||||
String = 6,
|
||||
Keyword = 7,
|
||||
Uuid = 8,
|
||||
Timestamp = 9,
|
||||
Bytes = 10,
|
||||
Tuple = 11,
|
||||
}
|
||||
|
||||
impl Display for AttributeTyping {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AttributeTyping::Ref => write!(f, "ref"),
|
||||
AttributeTyping::Component => write!(f, "component"),
|
||||
AttributeTyping::Bool => write!(f, "bool"),
|
||||
AttributeTyping::Int => write!(f, "int"),
|
||||
AttributeTyping::Float => write!(f, "float"),
|
||||
AttributeTyping::String => write!(f, "string"),
|
||||
AttributeTyping::Keyword => write!(f, "keyword"),
|
||||
AttributeTyping::Uuid => write!(f, "uuid"),
|
||||
AttributeTyping::Timestamp => write!(f, "timestamp"),
|
||||
AttributeTyping::Bytes => write!(f, "bytes"),
|
||||
AttributeTyping::Tuple => write!(f, "tuple"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for AttributeTyping {
|
||||
type Error = AttributeError;
|
||||
fn try_from(value: &'_ str) -> std::result::Result<Self, Self::Error> {
|
||||
use AttributeTyping::*;
|
||||
Ok(match value {
|
||||
"ref" => Ref,
|
||||
"component" => Component,
|
||||
"bool" => Bool,
|
||||
"int" => Int,
|
||||
"float" => Float,
|
||||
"string" => String,
|
||||
"keyword" => Keyword,
|
||||
"uuid" => Uuid,
|
||||
"timestamp" => Timestamp,
|
||||
"bytes" => Bytes,
|
||||
"tuple" => Tuple,
|
||||
s => {
|
||||
return Err(AttributeError::Conversion(
|
||||
s.to_string(),
|
||||
"AttributeTyping".to_string(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum TypeError {
|
||||
#[error("provided value {1} is not of type {0:?}")]
|
||||
Typing(AttributeTyping, String),
|
||||
}
|
||||
|
||||
impl AttributeTyping {
|
||||
fn type_err(&self, val: Value) -> TypeError {
|
||||
TypeError::Typing(*self, format!("{:?}", val))
|
||||
}
|
||||
pub(crate) fn coerce_value<'a>(&self, val: Value<'a>) -> Result<Value<'a>> {
|
||||
match self {
|
||||
AttributeTyping::Ref | AttributeTyping::Component => match val {
|
||||
val @ Value::EnId(_) => Ok(val),
|
||||
Value::Int(s) if s > 0 => Ok(Value::EnId(EntityId(s as u64))),
|
||||
val => Err(self.type_err(val).into()),
|
||||
},
|
||||
AttributeTyping::Bool => {
|
||||
if matches!(val, Value::Bool(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
AttributeTyping::Int => {
|
||||
if matches!(val, Value::Int(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
AttributeTyping::Float => match val {
|
||||
v @ Value::Float(_) => Ok(v),
|
||||
Value::Int(i) => Ok(Value::Float((i as f64).into())),
|
||||
val => Err(self.type_err(val).into()),
|
||||
},
|
||||
AttributeTyping::String => {
|
||||
if matches!(val, Value::String(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
AttributeTyping::Keyword => match val {
|
||||
val @ Value::Keyword(_) => Ok(val),
|
||||
Value::String(s) => Ok(Value::Keyword(Keyword::try_from(s.as_ref())?)),
|
||||
val => Err(self.type_err(val).into()),
|
||||
},
|
||||
AttributeTyping::Uuid => {
|
||||
if matches!(val, Value::Uuid(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
AttributeTyping::Timestamp => match val {
|
||||
val @ Value::Timestamp(_) => Ok(val),
|
||||
Value::Int(i) => Ok(Value::Timestamp(i)),
|
||||
val => Err(self.type_err(val).into()),
|
||||
},
|
||||
AttributeTyping::Bytes => {
|
||||
if matches!(val, Value::Bytes(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
AttributeTyping::Tuple => {
|
||||
if matches!(val, Value::Tuple(_)) {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(self.type_err(val).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, PartialEq, Ord, PartialOrd, Eq, Debug, Deserialize, Serialize)]
|
||||
pub(crate) enum AttributeIndex {
|
||||
None = 0,
|
||||
Indexed = 1,
|
||||
Unique = 2,
|
||||
Identity = 3,
|
||||
}
|
||||
|
||||
impl Display for AttributeIndex {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AttributeIndex::None => write!(f, "none"),
|
||||
AttributeIndex::Indexed => write!(f, "index"),
|
||||
AttributeIndex::Unique => write!(f, "unique"),
|
||||
AttributeIndex::Identity => write!(f, "identity"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for AttributeIndex {
|
||||
type Error = AttributeError;
|
||||
fn try_from(value: &'_ str) -> std::result::Result<Self, Self::Error> {
|
||||
use AttributeIndex::*;
|
||||
Ok(match value {
|
||||
"none" => None,
|
||||
"index" => Indexed,
|
||||
"unique" => Unique,
|
||||
"identity" => Identity,
|
||||
s => {
|
||||
return Err(AttributeError::Conversion(
|
||||
s.to_string(),
|
||||
"AttributeIndex".to_string(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Ord, PartialOrd, Eq, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct Attribute {
|
||||
#[serde(rename = "i")]
|
||||
pub(crate) id: AttrId,
|
||||
#[serde(rename = "n")]
|
||||
pub(crate) alias: Keyword,
|
||||
#[serde(rename = "c")]
|
||||
pub(crate) cardinality: AttributeCardinality,
|
||||
#[serde(rename = "t")]
|
||||
pub(crate) val_type: AttributeTyping,
|
||||
#[serde(rename = "u")]
|
||||
pub(crate) indexing: AttributeIndex,
|
||||
#[serde(rename = "h")]
|
||||
pub(crate) with_history: bool,
|
||||
}
|
||||
|
||||
const LARGE_VEC_SIZE: usize = 60;
|
||||
|
||||
impl Attribute {
|
||||
pub(crate) fn encode(&self) -> Encoded<LARGE_VEC_SIZE> {
|
||||
let mut inner = SmallVec::<[u8; LARGE_VEC_SIZE]>::new();
|
||||
self.serialize(&mut Serializer::new(&mut inner)).unwrap();
|
||||
Encoded { inner }
|
||||
}
|
||||
pub(crate) fn decode(data: &[u8]) -> Result<Self> {
|
||||
Ok(rmp_serde::from_slice(data)?)
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
use crate::data::attr::{Attribute, AttributeCardinality, AttributeIndex, AttributeTyping};
|
||||
use crate::data::id::{AttrId, EntityId, TxId};
|
||||
use crate::data::keyword::Keyword;
|
||||
use crate::data::value::Value;
|
||||
use serde_json::json;
|
||||
pub(crate) use serde_json::Value as JsonValue;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum JsonError {
|
||||
#[error("cannot convert JSON value {0} to {1}")]
|
||||
Conversion(JsonValue, String),
|
||||
#[error("missing field {1} in value {0}")]
|
||||
MissingField(JsonValue, String),
|
||||
}
|
||||
|
||||
impl From<JsonValue> for Value<'_> {
|
||||
fn from(v: JsonValue) -> Self {
|
||||
match v {
|
||||
JsonValue::Null => Value::Null,
|
||||
JsonValue::Bool(b) => Value::Bool(b),
|
||||
JsonValue::Number(n) => match n.as_i64() {
|
||||
Some(i) => Value::Int(i),
|
||||
None => match n.as_f64() {
|
||||
Some(f) => Value::Float(f.into()),
|
||||
None => Value::String(n.to_string().into()),
|
||||
},
|
||||
},
|
||||
JsonValue::String(s) => Value::String(s.into()),
|
||||
JsonValue::Array(arr) => Value::Tuple(arr.into_iter().map(Value::from).collect()),
|
||||
JsonValue::Object(d) => Value::Tuple(
|
||||
d.into_iter()
|
||||
.map(|(k, v)| Value::Tuple([Value::String(k.into()), Value::from(v)].into()))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Value<'_>> for JsonValue {
|
||||
fn from(v: Value<'_>) -> Self {
|
||||
match v {
|
||||
Value::Null => JsonValue::Null,
|
||||
Value::Bool(b) => JsonValue::Bool(b),
|
||||
Value::Int(i) => JsonValue::Number(i.into()),
|
||||
Value::Float(f) => json!(f.0),
|
||||
Value::String(t) => JsonValue::String(t.into_owned()),
|
||||
Value::Uuid(uuid) => JsonValue::String(uuid.to_string()),
|
||||
Value::Bytes(bytes) => JsonValue::String(base64::encode(bytes)),
|
||||
Value::Tuple(l) => {
|
||||
JsonValue::Array(l.iter().map(|v| JsonValue::from(v.clone())).collect())
|
||||
}
|
||||
Value::DescVal(v) => JsonValue::from(*v.0),
|
||||
Value::Bottom => JsonValue::Null,
|
||||
Value::EnId(i) => JsonValue::Number(i.0.into()),
|
||||
Value::Keyword(t) => JsonValue::String(t.to_string()),
|
||||
Value::Timestamp(i) => JsonValue::Number(i.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ JsonValue> for Keyword {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: &'_ JsonValue) -> Result<Self, Self::Error> {
|
||||
let s = value
|
||||
.as_str()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "Keyword".to_string()))?;
|
||||
Ok(Keyword::try_from(s)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ JsonValue> for Attribute {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'_ JsonValue) -> Result<Self, Self::Error> {
|
||||
let map = value
|
||||
.as_object()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "Attribute".to_string()))?;
|
||||
let id = match map.get("id") {
|
||||
None => AttrId(0),
|
||||
Some(v) => AttrId::try_from(v)?,
|
||||
};
|
||||
let alias = map
|
||||
.get("alias")
|
||||
.ok_or_else(|| JsonError::MissingField(value.clone(), "alias".to_string()))?;
|
||||
let alias = Keyword::try_from(alias)?;
|
||||
let cardinality = map
|
||||
.get("cardinality")
|
||||
.ok_or_else(|| JsonError::MissingField(value.clone(), "cardinality".to_string()))?
|
||||
.as_str()
|
||||
.ok_or_else(|| {
|
||||
JsonError::Conversion(value.clone(), "AttributeCardinality".to_string())
|
||||
})?;
|
||||
let cardinality = AttributeCardinality::try_from(cardinality)?;
|
||||
let val_type = map
|
||||
.get("type")
|
||||
.ok_or_else(|| JsonError::MissingField(value.clone(), "type".to_string()))?
|
||||
.as_str()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "AttributeTyping".to_string()))?;
|
||||
let val_type = AttributeTyping::try_from(val_type)?;
|
||||
|
||||
let indexing = match map.get("type") {
|
||||
None => AttributeIndex::None,
|
||||
Some(v) => AttributeIndex::try_from(v.as_str().ok_or_else(|| {
|
||||
JsonError::Conversion(value.clone(), "AttributeIndexing".to_string())
|
||||
})?)?,
|
||||
};
|
||||
|
||||
let with_history = match map.get("with_history") {
|
||||
None => true,
|
||||
Some(v) => v.as_bool().ok_or_else(|| {
|
||||
JsonError::Conversion(value.clone(), "AttributeWithHistory".to_string())
|
||||
})?,
|
||||
};
|
||||
|
||||
Ok(Attribute {
|
||||
id,
|
||||
alias,
|
||||
cardinality,
|
||||
val_type,
|
||||
indexing,
|
||||
with_history,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Attribute> for JsonValue {
|
||||
fn from(attr: Attribute) -> Self {
|
||||
json!({
|
||||
"id": attr.id.0,
|
||||
"alias": attr.alias.to_string(),
|
||||
"cardinality": attr.cardinality.to_string(),
|
||||
"type": attr.val_type.to_string(),
|
||||
"index": attr.indexing.to_string(),
|
||||
"with_history": attr.with_history
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttrId> for JsonValue {
|
||||
fn from(id: AttrId) -> Self {
|
||||
JsonValue::Number(id.0.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ JsonValue> for AttrId {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(value: &'_ JsonValue) -> Result<Self, Self::Error> {
|
||||
let v = value
|
||||
.as_u64()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "AttrId".to_string()))?;
|
||||
Ok(AttrId(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EntityId> for JsonValue {
|
||||
fn from(id: EntityId) -> Self {
|
||||
JsonValue::Number(id.0.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ JsonValue> for EntityId {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(value: &'_ JsonValue) -> Result<Self, Self::Error> {
|
||||
let v = value
|
||||
.as_u64()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "EntityId".to_string()))?;
|
||||
Ok(EntityId(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TxId> for JsonValue {
|
||||
fn from(id: TxId) -> Self {
|
||||
JsonValue::Number(id.0.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ JsonValue> for TxId {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(value: &'_ JsonValue) -> Result<Self, Self::Error> {
|
||||
let v = value
|
||||
.as_u64()
|
||||
.ok_or_else(|| JsonError::Conversion(value.clone(), "TxId".to_string()))?;
|
||||
Ok(TxId(v))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue