From 1b53e041e387482f935449ec563c2719328a8c07 Mon Sep 17 00:00:00 2001 From: Paul Z Date: Mon, 1 May 2023 02:08:22 +0200 Subject: [PATCH] new backend --- Cargo.lock | 35 +++ Cargo.toml | 1 + proto/themis.proto | 6 - src/graph.rs | 387 ++++++++++++++++++++---------- src/grpc_service.rs | 559 +++++++++++++++++++++++++------------------- src/main.rs | 17 +- src/relation_set.rs | 449 +++++++++++++++++++++++++++++++++++ 7 files changed, 1073 insertions(+), 381 deletions(-) create mode 100644 src/relation_set.rs diff --git a/Cargo.lock b/Cargo.lock index 4985a44..b65b45f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.79" @@ -157,6 +166,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "compact_str" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff0805f79ecb1b35163f3957a6934ea8d04fcd36ef98b52e7316f63e72e73d1" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -902,6 +924,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + [[package]] name = "scopeguard" version = "1.1.0" @@ -989,6 +1017,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.109" @@ -1043,6 +1077,7 @@ dependencies = [ name = "themis" version = "0.1.0" dependencies = [ + "compact_str", "dotenvy", "hex", "log", diff --git a/Cargo.toml b/Cargo.toml index dd329bf..3871cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ tonic = { version="0.9.2", features=["tls"] } prost = "0.11.9" sha2 = "0.10.6" hex = "0.4.3" +compact_str = "0.7.0" [build-dependencies] tonic-build = "0.9.2" diff --git a/proto/themis.proto b/proto/themis.proto index 2f16b25..ae2046d 100644 --- a/proto/themis.proto +++ b/proto/themis.proto @@ -1,12 +1,6 @@ syntax = "proto3"; package eu.zettoit.themis; -service ObjectService{ - rpc Create(Object) returns (Empty); - rpc Delete(Object) returns (Empty); - rpc Exists(Object) returns (ExistsResponse); -} - service RelationService { rpc Create(Relation) returns (Empty); rpc Delete(Relation) returns (Empty); diff --git a/src/graph.rs b/src/graph.rs index 6798684..500fc61 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,4 +1,5 @@ use std::{ + cmp::Ordering, collections::{ hash_map::{Iter, IterMut}, BinaryHeap, HashMap, HashSet, @@ -33,12 +34,12 @@ pub struct ObjectRef(pub u32); #[derive(PartialEq, Eq, Hash, Clone, Debug, Deserialize, Serialize)] pub enum ObjectOrSet { - Object(ObjectRef), - Set((ObjectRef, Relation)), + Object(Object), + Set((Object, Relation)), } #[derive(Hash, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] -pub struct Relation(String); +pub struct Relation(pub String); #[derive(PartialEq, Eq, Clone, Hash, Serialize, Deserialize, Debug)] pub struct ObjectRelation(pub ObjectRef, pub Relation); @@ -53,7 +54,7 @@ impl Object { } impl ObjectOrSet { - pub fn object_ref(&self) -> &ObjectRef { + pub fn object(&self) -> &Object { match self { ObjectOrSet::Object(obj) => obj, ObjectOrSet::Set((obj, _)) => obj, @@ -67,14 +68,14 @@ impl ObjectOrSet { } } -impl From for ObjectOrSet { - fn from(value: ObjectRef) -> Self { +impl From for ObjectOrSet { + fn from(value: Object) -> Self { Self::Object(value) } } -impl From<(ObjectRef, &str)> for ObjectOrSet { - fn from(value: (ObjectRef, &str)) -> Self { +impl From<(Object, &str)> for ObjectOrSet { + fn from(value: (Object, &str)) -> Self { Self::Set((value.0, Relation::new(value.1))) } } @@ -102,6 +103,27 @@ impl From<(ObjectRef, &str)> for ObjectRelation { Self(value.0, Relation::new(value.1)) } } +impl From<(&str, &str)> for Object { + fn from((namespace, id): (&str, &str)) -> Self { + Self { + namespace: namespace.to_string(), + id: id.to_string(), + } + } +} +impl From<(&String, &String)> for Object { + fn from((namespace, id): (&String, &String)) -> Self { + Self { + namespace: namespace.to_string(), + id: id.to_string(), + } + } +} +impl From<(String, String)> for Object { + fn from((namespace, id): (String, String)) -> Self { + Self { namespace, id } + } +} impl Graph { pub fn get_node(&self, namespace: &str, id: &str) -> Option { @@ -110,29 +132,86 @@ impl Graph { pub fn object_from_ref(&self, obj: &ObjectRef) -> Object { self.nodes.get_by_b(obj).unwrap().clone() } - pub fn add_node(&mut self, node: Object) -> ObjectRef { + pub fn get_or_add_node(&mut self, namespace: &str, id: &str) -> ObjectRef { + if let Some(node) = self.get_node(namespace, id) { + node + } else { + self.add_node((namespace, id)) + } + } + pub fn add_node(&mut self, node: impl Into) -> ObjectRef { let obj_ref = ObjectRef(self.counter); - self.nodes.insert(node, obj_ref); + self.nodes.insert(node.into(), obj_ref); self.counter += 1; obj_ref } - pub fn remove_node(&mut self, node: Object) { + pub fn remove_node(&mut self, node: impl Into) { + let node = node.into(); let index = self.nodes.remove_by_a(&node); if let Some(index) = index { self.edges.remove_by_c(&index); - self.edges.get_by_a(&ObjectOrSet::Object(*index)); + //self.edges.get_by_a(&ObjectOrSet::Object(*index)); //TODO: remove edges with ObjectOrSet::Set } } + pub fn remove_node_by_ref(&mut self, node: impl Into) { + let node = node.into(); + let index = self.nodes.remove_by_b(&node); + if index.is_some() { + self.edges.remove_by_c(&node); + + //let edges = self + // .edges + // .left_to_right + // .keys() + // .filter(|x| *x.object_ref() == node) + // .map(|x| (**x).clone()) + // .collect::>(); + //for edge in edges { + // self.edges.remove_by_a(&edge); + //} + } + } pub fn has_relation(&self, src: ObjectOrSet, dst: ObjectRelation) -> bool { self.edges.has(&src, &dst.1, &dst.0) } - pub fn add_relation(&mut self, src: ObjectOrSet, dst: ObjectRelation) { - self.edges.insert(src, dst.1, dst.0); + pub fn add_relation(&mut self, src: impl Into, dst: impl Into) { + let dst = dst.into(); + self.edges.insert(src.into(), dst.1, dst.0); } - pub fn remove_relation(&mut self, src: ObjectOrSet, dst: ObjectRelation) { + pub fn remove_relation(&mut self, src: impl Into, dst: impl Into) { + let dst = dst.into(); + self.edges.remove(&src.into(), &dst.1, &dst.0); + } + pub fn remove_relation_and_residual_node( + &mut self, + src: impl Into, + dst: impl Into, + ) { + let src = src.into(); + let dst = dst.into(); self.edges.remove(&src, &dst.1, &dst.0); + + //if self.edges.get_by_c(src.object_ref()).is_empty() + // && !self + // .edges + // .left_to_right + // .keys() + // .any(|x| x.object_ref() == src.object_ref()) + //{ + // self.remove_node_by_ref(*src.object_ref()); + //} + + //if self.edges.get_by_c(&dst.0).is_empty() + // && !self + // .edges + // .left_to_right + // .keys() + // .any(|x| *x.object_ref() == dst.0) + //{ + // self.remove_node_by_ref(dst.0); + //} } pub fn is_related_to( @@ -158,87 +237,88 @@ impl Graph { q.push(ObjectRelationDist(1, neighbor.clone())); } - while let Some(ObjectRelationDist(node_dist, node)) = q.pop() { - let node_dist = node_dist + 1; - let node = ObjectOrSet::Set((node.0, node.1)); - for neighbor in self - .edges - .get_by_a(&node) - .iter() - .flat_map(|(r, m)| m.iter().map(|x| ObjectRelation(**x, (**r).clone()))) - { - if neighbor == dst { - return true; - } - if let Some(existing_node_dist) = dist.get(&neighbor) { - if *existing_node_dist < node_dist { - continue; - } - } - dist.insert(neighbor.clone(), node_dist); - q.push(ObjectRelationDist(node_dist, neighbor.clone())); - } - } + //while let Some(ObjectRelationDist(node_dist, node)) = q.pop() { + // let node_dist = node_dist + 1; + // let node = ObjectOrSet::Set((node.0, node.1)); + // for neighbor in self + // .edges + // .get_by_a(&node) + // .iter() + // .flat_map(|(r, m)| m.iter().map(|x| ObjectRelation(**x, (**r).clone()))) + // { + // if neighbor == dst { + // return true; + // } + // if let Some(existing_node_dist) = dist.get(&neighbor) { + // if *existing_node_dist < node_dist { + // continue; + // } + // } + // dist.insert(neighbor.clone(), node_dist); + // q.push(ObjectRelationDist(node_dist, neighbor.clone())); + // } + //} false } pub fn related_to(&self, dst: ObjectRef, relation: Relation) -> HashSet { - let mut relation_sets = vec![]; - let mut relations: HashSet = HashSet::new(); - for obj in self.edges.get_by_cb(&dst, &relation) { - match obj { - ObjectOrSet::Object(obj) => { - relations.insert(*obj); - } - ObjectOrSet::Set(set) => relation_sets.push(set), - } - } - while let Some(set) = relation_sets.pop() { - for obj in self.edges.get_by_cb(&set.0, &set.1) { - match obj { - ObjectOrSet::Object(obj) => { - relations.insert(*obj); - } - ObjectOrSet::Set(set) => relation_sets.push(set), - } - } - } - relations + //let mut relation_sets = vec![]; + //let mut relations: HashSet = HashSet::new(); + //for obj in self.edges.get_by_cb(&dst, &relation) { + // match obj { + // ObjectOrSet::Object(obj) => { + // relations.insert(*obj); + // } + // ObjectOrSet::Set(set) => relation_sets.push(set), + // } + //} + //while let Some(set) = relation_sets.pop() { + // for obj in self.edges.get_by_cb(&set.0, &set.1) { + // match obj { + // ObjectOrSet::Object(obj) => { + // relations.insert(*obj); + // } + // ObjectOrSet::Set(set) => relation_sets.push(set), + // } + // } + //} + //relations + todo!() } pub fn relations(&self, src: impl Into) -> HashSet { - let src: ObjectRelation = src.into(); + //let src: ObjectRelation = src.into(); - let mut visited = HashSet::new(); - let mut relation_sets = vec![]; - let mut relations = HashSet::new(); + //let mut visited = HashSet::new(); + //let mut relation_sets = vec![]; + //let mut relations = HashSet::new(); - for (rel, neighbors) in self.edges.get_by_a(&ObjectOrSet::Object(src.0)) { - for neighbor in neighbors { - if *rel == src.1 { - relations.insert(*neighbor); - } - relation_sets.push((rel, neighbor)); - } - } + //for (rel, neighbors) in self.edges.get_by_a(&ObjectOrSet::Object(src.0)) { + // for neighbor in neighbors { + // if *rel == src.1 { + // relations.insert(*neighbor); + // } + // relation_sets.push((rel, neighbor)); + // } + //} - while let Some((rel, obj_ref)) = relation_sets.pop() { - if !visited.contains(&(rel, obj_ref)) { - for (rel, neighbors) in self - .edges - .get_by_a(&ObjectOrSet::Set((*obj_ref, (*rel).clone()))) - { - for neighbor in neighbors { - if *rel == src.1 { - relations.insert(*neighbor); - } - relation_sets.push((rel, neighbor)); - } - } - visited.insert((rel, obj_ref)); - } - } - - relations + //while let Some((rel, obj_ref)) = relation_sets.pop() { + // if !visited.contains(&(rel, obj_ref)) { + // for (rel, neighbors) in self + // .edges + // .get_by_a(&ObjectOrSet::Set((*obj_ref, (*rel).clone()))) + // { + // for neighbor in neighbors { + // if *rel == src.1 { + // relations.insert(*neighbor); + // } + // relation_sets.push((rel, neighbor)); + // } + // } + // visited.insert((rel, obj_ref)); + // } + //} + //relations + todo!() } pub async fn to_file(&self, file: &mut File) { @@ -247,33 +327,33 @@ impl Graph { file.write_all(format!("[{}:{}]\n", &obj.namespace, &obj.id).as_bytes()) .await .unwrap(); - for (rel, arr) in self.edges.get_by_c(obj_ref.as_ref()) { - let arr = arr - .iter() - .filter_map(|x| { - let rel_obj_ref = x.object_ref(); - self.nodes.get_by_b(rel_obj_ref).map(|rel_obj| { - let (namespace, id) = (&rel_obj.namespace, &rel_obj.id); + //for (rel, arr) in self.edges.get_by_c(obj_ref.as_ref()) { + // let arr = arr + // .iter() + // .filter_map(|x| { + // let rel_obj_ref = x.object_ref(); + // self.nodes.get_by_b(rel_obj_ref).map(|rel_obj| { + // let (namespace, id) = (&rel_obj.namespace, &rel_obj.id); - if *namespace == obj.namespace && *id == obj.id { - match x.relation() { - None => "self".to_string(), - Some(rel) => format!("self#{}", &rel.0), - } - } else { - match x.relation() { - None => format!("{}:{}", &namespace, &id), - Some(rel) => format!("{}:{}#{}", &namespace, &id, &rel.0), - } - } - }) - }) - .reduce(|acc, e| acc + ", " + &e) - .unwrap_or_default(); - file.write_all(format!("{} = [{}]\n", &rel.0, &arr).as_bytes()) - .await - .unwrap(); - } + // if *namespace == obj.namespace && *id == obj.id { + // match x.relation() { + // None => "self".to_string(), + // Some(rel) => format!("self#{}", &rel.0), + // } + // } else { + // match x.relation() { + // None => format!("{}:{}", &namespace, &id), + // Some(rel) => format!("{}:{}#{}", &namespace, &id, &rel.0), + // } + // } + // }) + // }) + // .reduce(|acc, e| acc + ", " + &e) + // .unwrap_or_default(); + // file.write_all(format!("{} = [{}]\n", &rel.0, &arr).as_bytes()) + // .await + // .unwrap(); + //} file.write_all("\n".as_bytes()).await.unwrap(); } } @@ -339,19 +419,19 @@ impl Graph { } } - for relation in relations { - let src = match relation.2 { - Some(rel) => { - let obj = graph.get_node(&relation.0, &relation.1).unwrap(); - ObjectOrSet::Set((obj, Relation::new(&rel))) - } - None => { - let obj = graph.get_node(&relation.0, &relation.1).unwrap(); - ObjectOrSet::Object(obj) - } - }; - graph.add_relation(src, ObjectRelation(relation.3, Relation(relation.4))); - } + //for relation in relations { + // let src = match relation.2 { + // Some(rel) => { + // let obj = graph.get_node(&relation.0, &relation.1).unwrap(); + // ObjectOrSet::Set((obj, Relation::new(&rel))) + // } + // None => { + // let obj = graph.get_node(&relation.0, &relation.1).unwrap(); + // ObjectOrSet::Object(obj) + // } + // }; + // graph.add_relation(src, ObjectRelation(relation.3, Relation(relation.4))); + //} graph } @@ -598,3 +678,58 @@ impl Default for BidThreeMap { } } } + +//impl Ord for ObjectOrSet { +// fn cmp(&self, other: &Self) -> Ordering {} +//} +// +//impl Ord for ObjectRef {}cmp + +impl PartialOrd for Relation { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} +impl Ord for Relation { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for ObjectOrSet { + fn partial_cmp(&self, other: &Self) -> Option { + match ( + self.object().partial_cmp(other.object()), + self.relation(), + other.relation(), + ) { + (Some(Ordering::Equal), Some(self_rel), Some(other_rel)) => { + self_rel.partial_cmp(other_rel) + } + (ord, _, _) => ord, + } + } +} +impl Ord for ObjectOrSet { + fn cmp(&self, other: &Self) -> Ordering { + self.object() + .cmp(other.object()) + .then(self.relation().cmp(&other.relation())) + } +} + +impl PartialOrd for Object { + fn partial_cmp(&self, other: &Self) -> Option { + match self.namespace.partial_cmp(&other.namespace) { + Some(core::cmp::Ordering::Equal) => self.id.partial_cmp(&other.id), + ord => ord, + } + } +} +impl Ord for Object { + fn cmp(&self, other: &Self) -> Ordering { + self.namespace + .cmp(&other.namespace) + .then(self.id.cmp(&other.id)) + } +} diff --git a/src/grpc_service.rs b/src/grpc_service.rs index 6e8ed88..c3d1f11 100644 --- a/src/grpc_service.rs +++ b/src/grpc_service.rs @@ -8,125 +8,91 @@ use tokio::sync::Mutex; use tonic::metadata::MetadataMap; use tonic::{Request, Response, Status}; -use crate::graph::{self, Graph, ObjectRelation}; +use crate::relation_set::{ObjectOrSet, RelationSet}; use crate::themis_proto::{ - object_service_server::ObjectService, query_service_server::QueryService, relation::Src, - relation_service_server::RelationService, Empty, ExistsResponse, GetRelatedToResponse, - GetRelationsRequest, GetRelationsResponse, IsRelatedToResponse, Object, Relation, Set, + query_service_server::QueryService, relation::Src, relation_service_server::RelationService, + Empty, ExistsResponse, GetRelatedToResponse, GetRelationsRequest, GetRelationsResponse, + IsRelatedToResponse, Relation, Set, }; #[derive(Clone)] pub struct GraphService { pub api_keys: Arc>>, - pub graph: Arc>, + pub graph: Arc>, pub save_trigger: Sender<()>, } -#[tonic::async_trait] -impl ObjectService for GraphService { - async fn create(&self, request: Request) -> Result, Status> { - let mut graph = self.graph.lock().await; - - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &request.get_ref().namespace, - "write", - ) - .await?; - - if request.get_ref().namespace.is_empty() || request.get_ref().id.is_empty() { - return Err(Status::invalid_argument("namespace and id must be set")); - } - - graph.add_node(graph::Object::new( - &request.get_ref().namespace, - &request.get_ref().id, - )); - - info!( - "created object {}:{}", - &request.get_ref().namespace, - &request.get_ref().id - ); - - self.save_trigger.send(()).await.unwrap(); - - Ok(Response::new(Empty {})) - } - async fn delete(&self, request: Request) -> Result, Status> { - let mut graph = self.graph.lock().await; - - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &request.get_ref().namespace, - "write", - ) - .await?; - - if request.get_ref().namespace.is_empty() || request.get_ref().id.is_empty() { - return Err(Status::invalid_argument("namespace and id must be set")); - } - - graph.remove_node(graph::Object::new( - &request.get_ref().namespace, - &request.get_ref().id, - )); - - info!( - "removed object {}:{}", - &request.get_ref().namespace, - &request.get_ref().id - ); - - self.save_trigger.send(()).await.unwrap(); - - Ok(Response::new(Empty {})) - } - async fn exists(&self, request: Request) -> Result, Status> { - let graph = self.graph.lock().await; - - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &request.get_ref().namespace, - "read", - ) - .await?; - - if request.get_ref().namespace.is_empty() || request.get_ref().id.is_empty() { - return Err(Status::invalid_argument("namespace and id must be set")); - } - - let exists = graph - .get_node(&request.get_ref().namespace, &request.get_ref().id) - .is_some(); - - Ok(Response::new(ExistsResponse { exists })) - } -} - #[tonic::async_trait] impl RelationService for GraphService { async fn create(&self, request: Request) -> Result, Status> { let mut graph = self.graph.lock().await; - let (src, dst, dst_namespace) = transform_relation(request.get_ref(), &graph)?; + let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &dst_namespace, + let req_src = request + .get_ref() + .src + .as_ref() + .ok_or(Status::invalid_argument("src must be set"))?; + let req_dst = request + .get_ref() + .dst + .as_ref() + .ok_or(Status::invalid_argument("dst must be set"))?; + let req_rel = &request.get_ref().relation; + + if req_rel.is_empty() { + return Err(Status::invalid_argument("relation must be set")); + } + if req_dst.namespace.is_empty() { + return Err(Status::invalid_argument("dst.namespace must be set")); + } + if req_dst.id.is_empty() { + return Err(Status::invalid_argument("dst.id must be set")); + } + + if !graph.has( + ("themis_key", &*api_key), "write", - ) - .await?; + ("themis_ns", &*req_dst.namespace), + ) { + return Err(Status::permission_denied( + "missing dst.namespace write permissions", + ))?; + } - graph.add_relation(src, dst); + let src: Result = match req_src { + Src::SrcObj(obj) => { + if obj.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if obj.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + + Ok((&*obj.namespace, &*obj.id).into()) + } + Src::SrcSet(set) => { + if set.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if set.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + if set.relation.is_empty() { + return Err(Status::invalid_argument("src.relation must be set")); + } + + Ok((&*set.namespace, &*set.id, &*set.relation).into()) + } + }; + let src = src?; + + graph.insert( + src.clone(), + req_rel.clone(), + (req_dst.namespace.clone(), req_dst.id.clone()), + ); info!("created relation"); @@ -137,20 +103,69 @@ impl RelationService for GraphService { async fn delete(&self, request: Request) -> Result, Status> { let mut graph = self.graph.lock().await; - let (src, dst, dst_namespace) = transform_relation(request.get_ref(), &graph)?; + let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &dst_namespace, + let req_src = request + .get_ref() + .src + .as_ref() + .ok_or(Status::invalid_argument("src must be set"))?; + let req_dst = request + .get_ref() + .dst + .as_ref() + .ok_or(Status::invalid_argument("dst must be set"))?; + let req_rel = &request.get_ref().relation; + + if req_rel.is_empty() { + return Err(Status::invalid_argument("relation must be set")); + } + if req_dst.namespace.is_empty() { + return Err(Status::invalid_argument("dst.namespace must be set")); + } + if req_dst.id.is_empty() { + return Err(Status::invalid_argument("dst.id must be set")); + } + + if !graph.has( + ("themis_key", &*api_key), "write", - ) - .await?; + ("themis_ns", &*req_dst.namespace), + ) { + return Err(Status::permission_denied( + "missing dst.namespace write permissions", + ))?; + } + let src: Result = match req_src { + Src::SrcObj(obj) => { + if obj.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if obj.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } - graph.remove_relation(src, dst); + Ok((&*obj.namespace, &*obj.id).into()) + } + Src::SrcSet(set) => { + if set.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if set.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + if set.relation.is_empty() { + return Err(Status::invalid_argument("src.relation must be set")); + } - info!("removed relation relation"); + Ok((&*set.namespace, &*set.id, &*set.relation).into()) + } + }; + let src = src?; + + graph.remove(src, req_rel.as_str(), (&*req_dst.namespace, &*req_dst.id)); + + info!("deleted relation"); self.save_trigger.send(()).await.unwrap(); @@ -159,18 +174,67 @@ impl RelationService for GraphService { async fn exists(&self, request: Request) -> Result, Status> { let graph = self.graph.lock().await; - let (src, dst, dst_namespace) = transform_relation(request.get_ref(), &graph)?; + let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &dst_namespace, + let req_src = request + .get_ref() + .src + .as_ref() + .ok_or(Status::invalid_argument("src must be set"))?; + let req_dst = request + .get_ref() + .dst + .as_ref() + .ok_or(Status::invalid_argument("dst must be set"))?; + let req_rel = &request.get_ref().relation; + + if req_rel.is_empty() { + return Err(Status::invalid_argument("relation must be set")); + } + if req_dst.namespace.is_empty() { + return Err(Status::invalid_argument("dst.namespace must be set")); + } + if req_dst.id.is_empty() { + return Err(Status::invalid_argument("dst.id must be set")); + } + + if !graph.has( + ("themis_key", &*api_key), "read", - ) - .await?; + ("themis_ns", &*req_dst.namespace), + ) { + return Err(Status::permission_denied( + "missing dst.namespace write permissions", + ))?; + } + let src: Result = match req_src { + Src::SrcObj(obj) => { + if obj.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if obj.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } - let exists = graph.has_relation(src, dst); + Ok((&*obj.namespace, &*obj.id).into()) + } + Src::SrcSet(set) => { + if set.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if set.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + if set.relation.is_empty() { + return Err(Status::invalid_argument("src.relation must be set")); + } + + Ok((&*set.namespace, &*set.id, &*set.relation).into()) + } + }; + let src = src?; + + let exists = graph.has(src, req_rel.as_str(), (&*req_dst.namespace, &*req_dst.id)); Ok(Response::new(ExistsResponse { exists })) } @@ -184,11 +248,73 @@ impl QueryService for GraphService { ) -> Result, Status> { let graph = self.graph.lock().await; - let related = if let Ok((src, dst, _)) = transform_relation(request.get_ref(), &graph) { - graph.is_related_to(src, dst) - } else { - false + let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; + + let req_src = request + .get_ref() + .src + .as_ref() + .ok_or(Status::invalid_argument("src must be set"))?; + let req_dst = request + .get_ref() + .dst + .as_ref() + .ok_or(Status::invalid_argument("dst must be set"))?; + let req_rel = &request.get_ref().relation; + + if req_rel.is_empty() { + return Err(Status::invalid_argument("relation must be set")); + } + if req_dst.namespace.is_empty() { + return Err(Status::invalid_argument("dst.namespace must be set")); + } + if req_dst.id.is_empty() { + return Err(Status::invalid_argument("dst.id must be set")); + } + + if !graph.has( + ("themis_key", &*api_key), + "read", + ("themis_ns", &*req_dst.namespace), + ) { + return Err(Status::permission_denied( + "missing dst.namespace write permissions", + ))?; + } + + let src: Result = match req_src { + Src::SrcObj(obj) => { + if obj.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if obj.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + + Ok((&*obj.namespace, &*obj.id).into()) + } + Src::SrcSet(set) => { + if set.namespace.is_empty() { + return Err(Status::invalid_argument("src.namespace must be set")); + } + if set.id.is_empty() { + return Err(Status::invalid_argument("src.id must be set")); + } + if set.relation.is_empty() { + return Err(Status::invalid_argument("src.relation must be set")); + } + + Ok((&*set.namespace, &*set.id, &*set.relation).into()) + } }; + let src = src?; + + let related = graph.has_recursive( + src, + req_rel.as_str(), + (&*req_dst.namespace, &*req_dst.id), + u32::MAX, + ); Ok(Response::new(IsRelatedToResponse { related })) } @@ -196,127 +322,91 @@ impl QueryService for GraphService { &self, request: Request, ) -> Result, Status> { - let graph = self.graph.lock().await; + //let graph = self.graph.lock().await; - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &request.get_ref().namespace, - "read", - ) - .await?; + //authenticate( + // request.metadata(), + // &graph, + // &self.api_keys, + // &request.get_ref().namespace, + // "read", + //) + //.await?; - let obj = graph - .get_node(&request.get_ref().namespace, &request.get_ref().id) - .ok_or(Status::not_found("object not found"))?; + //let obj = graph + // .get_node(&request.get_ref().namespace, &request.get_ref().id) + // .ok_or(Status::not_found("object not found"))?; - let rel = graph::Relation::new(&request.get_ref().relation); + //let rel = graph::Relation::new(&request.get_ref().relation); - Ok(Response::new(GetRelatedToResponse { - objects: graph - .related_to(obj, rel) - .into_iter() - .map(|x| { - let obj = graph.object_from_ref(&x); - Object { - namespace: obj.namespace.to_string(), - id: obj.id, - } - }) - .collect::>(), - })) + //Ok(Response::new(GetRelatedToResponse { + // objects: graph + // .related_to(obj, rel) + // .into_iter() + // .map(|x| { + // let obj = graph.object_from_ref(&x); + // Object { + // namespace: obj.namespace.to_string(), + // id: obj.id, + // } + // }) + // .collect::>(), + //})) + todo!() } async fn get_relations( &self, request: Request, ) -> Result, Status> { - let graph = self.graph.lock().await; + //let graph = self.graph.lock().await; - if request.get_ref().relation.is_empty() { - return Err(Status::invalid_argument("relation must be set")); - } + //if request.get_ref().relation.is_empty() { + // return Err(Status::invalid_argument("relation must be set")); + //} - let obj = request - .get_ref() - .object - .as_ref() - .ok_or(Status::invalid_argument("object must be set"))?; + //let obj = request + // .get_ref() + // .object + // .as_ref() + // .ok_or(Status::invalid_argument("object must be set"))?; - authenticate( - request.metadata(), - &graph, - &self.api_keys, - &obj.namespace, - "read", - ) - .await?; + //authenticate( + // request.metadata(), + // &graph, + // &self.api_keys, + // &obj.namespace, + // "read", + //) + //.await?; - let obj = graph - .get_node(&obj.namespace, &obj.id) - .ok_or(Status::not_found("object not found"))?; + //let obj = graph + // .get_node(&obj.namespace, &obj.id) + // .ok_or(Status::not_found("object not found"))?; - Ok(Response::new(GetRelationsResponse { - objects: graph - .relations(ObjectRelation( - obj, - graph::Relation::new(&request.get_ref().relation), - )) - .into_iter() - .map(|x| { - let obj = graph.object_from_ref(&x); - Object { - namespace: obj.namespace.to_string(), - id: obj.id, - } - }) - .collect::>(), - })) + //Ok(Response::new(GetRelationsResponse { + // objects: graph + // .relations(ObjectRelation( + // obj, + // graph::Relation::new(&request.get_ref().relation), + // )) + // .into_iter() + // .map(|x| { + // let obj = graph.object_from_ref(&x); + // Object { + // namespace: obj.namespace.to_string(), + // id: obj.id, + // } + // }) + // .collect::>(), + //})) + todo!() } } -fn transform_relation( - rel: &Relation, - graph: &Graph, -) -> Result<(graph::ObjectOrSet, graph::ObjectRelation, String), Status> { - let src = match rel - .src - .as_ref() - .ok_or(Status::invalid_argument("src must be set"))? - { - Src::SrcObj(object) => graph::ObjectOrSet::Object( - graph - .get_node(&object.namespace, &object.id) - .ok_or(Status::not_found("src object could not be found"))?, - ), - Src::SrcSet(set) => graph::ObjectOrSet::Set(( - graph - .get_node(&set.namespace, &set.id) - .ok_or(Status::not_found("src object could not be found"))?, - graph::Relation::new(&set.relation), - )), - }; - - let dst = rel - .dst - .as_ref() - .ok_or(Status::invalid_argument("dst must be set"))?; - let dst_namespace = dst.namespace.to_string(); - let dst = graph - .get_node(&dst.namespace, &dst.id) - .ok_or(Status::not_found("dst object could not be found"))?; - let dst = ObjectRelation(dst, graph::Relation::new(&rel.relation)); - - Ok((src, dst, dst_namespace)) -} - -async fn authenticate( +async fn api_key_from_req( metadata: &MetadataMap, - graph: &Graph, api_keys: &Arc>>, - namespace: &str, - relation: &str, -) -> Result<(), Status> { +) -> Result { let api_key = metadata .get("x-api-key") .map(|x| x.to_str().unwrap()) @@ -329,18 +419,5 @@ async fn authenticate( let api_key = api_keys .get(&api_key) .ok_or(Status::unauthenticated("api-key invalid"))?; - - let api_key = graph - .get_node("themis_key", api_key) - .ok_or(Status::unauthenticated("api-key invalid"))?; - - let ns_ref = graph - .get_node("themis_ns", namespace) - .ok_or(Status::permission_denied("no permission for namespace"))?; - - if !graph.is_related_to(api_key, (ns_ref, graph::Relation::new(relation))) { - Err(Status::permission_denied("no permission for namespace")) - } else { - Ok(()) - } + Ok(api_key.to_string()) } diff --git a/src/main.rs b/src/main.rs index 2d058a8..ac3f79a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ +#![feature(btree_cursors)] + use std::{collections::HashMap, sync::Arc, time::Duration}; -use graph::Graph; use grpc_service::GraphService; +use relation_set::RelationSet; +//use grpc_service::GraphService; use tokio::{ fs::{self, File}, io::{AsyncBufReadExt, BufReader}, @@ -10,13 +13,12 @@ use tokio::{ }; use tonic::transport::Server; -pub mod graph; pub mod grpc_service; +pub mod relation_set; pub mod themis_proto; use crate::themis_proto::{ - object_service_server::ObjectServiceServer, query_service_server::QueryServiceServer, - relation_service_server::RelationServiceServer, + query_service_server::QueryServiceServer, relation_service_server::RelationServiceServer, }; #[tokio::main] @@ -38,9 +40,9 @@ async fn main() { } let graph = if let Ok(mut file) = File::open("graph.dat").await { - Graph::from_file(&mut file).await + RelationSet::from_file(&mut file).await } else { - Graph::default() + RelationSet::new() }; let graph = Arc::new(Mutex::new(graph)); @@ -68,10 +70,9 @@ async fn main() { }; Server::builder() - .add_service(ObjectServiceServer::new(graph_service.clone())) .add_service(RelationServiceServer::new(graph_service.clone())) .add_service(QueryServiceServer::new(graph_service)) - .serve("0.0.0.0:50051".parse().unwrap()) + .serve("[::]:50051".parse().unwrap()) .await .unwrap() } diff --git a/src/relation_set.rs b/src/relation_set.rs new file mode 100644 index 0000000..06adfc4 --- /dev/null +++ b/src/relation_set.rs @@ -0,0 +1,449 @@ +use std::{ + cmp::Ordering, + collections::{BTreeMap, BinaryHeap, HashMap, HashSet}, + ops::{Bound, Deref}, + sync::Arc, +}; + +use compact_str::CompactString; +use tokio::{ + fs::File, + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, +}; + +#[derive(Hash, PartialEq, Eq, Clone, Debug)] +pub struct Object { + pub namespace: CompactString, + pub id: CompactString, +} + +#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] +pub struct ObjectRef<'a> { + pub namespace: &'a str, + pub id: &'a str, +} + +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub enum ObjectOrSet { + Object(Object), + Set((Object, Relation)), +} +#[derive(Hash, PartialEq, Eq, Clone, Debug)] +pub struct Relation(pub CompactString); + +type S = ObjectOrSet; +type R = Relation; +type D = Object; + +pub struct RelationSet { + src_to_dst: BTreeMap, HashMap, HashSet>>>, + dst_to_src: BTreeMap, HashMap, HashSet>>>, +} + +impl RelationSet { + pub fn new() -> Self { + Self { + src_to_dst: BTreeMap::new(), + dst_to_src: BTreeMap::new(), + } + } + + pub fn insert(&mut self, src: impl Into, rel: impl Into, dst: impl Into) { + let src = Arc::new(src.into()); + let rel = Arc::new(rel.into()); + let dst = Arc::new(dst.into()); + + if let Some(rels_dsts) = self.src_to_dst.get_mut(&src) { + if let Some(dsts) = rels_dsts.get_mut(&rel) { + dsts.insert(dst.clone()); + } else { + let mut dsts = HashSet::new(); + dsts.insert(dst.clone()); + rels_dsts.insert(rel.clone(), dsts); + } + } else { + let mut rels_dsts = HashMap::new(); + let mut dsts = HashSet::new(); + dsts.insert(dst.clone()); + rels_dsts.insert(rel.clone(), dsts); + self.src_to_dst.insert(src.clone(), rels_dsts); + } + + if let Some(rels_srcs) = self.dst_to_src.get_mut(&dst) { + if let Some(srcs) = rels_srcs.get_mut(&rel) { + srcs.insert(src.clone()); + } else { + let mut srcs = HashSet::new(); + srcs.insert(src.clone()); + rels_srcs.insert(rel.clone(), srcs); + } + } else { + let mut rels_srcs = HashMap::new(); + let mut srcs = HashSet::new(); + srcs.insert(src.clone()); + rels_srcs.insert(rel.clone(), srcs); + self.dst_to_src.insert(dst.clone(), rels_srcs); + } + } + + pub fn remove(&mut self, src: impl Into, rel: impl Into, dst: impl Into) { + let src = src.into(); + let rel = rel.into(); + let dst = dst.into(); + + if let Some(dsts) = self + .src_to_dst + .get_mut(&src) + .and_then(|rels_dsts| rels_dsts.get_mut(&rel)) + { + dsts.remove(&dst); + } + + if let Some(srcs) = self + .dst_to_src + .get_mut(&dst) + .and_then(|rels_srcs| rels_srcs.get_mut(&rel)) + { + srcs.remove(&src); + } + } + + pub fn remove_by_src(&mut self, src: &S) { + for (rel, dsts) in self.src_to_dst.remove(src).iter().flat_map(|x| x.iter()) { + for dst in dsts { + if let Some(srcs) = self + .dst_to_src + .get_mut(dst) + .and_then(|rels_srcs| rels_srcs.get_mut(rel)) + { + srcs.remove(src); + } + } + } + } + + pub fn remove_by_dst(&mut self, dst: &D) { + for (rel, srcs) in self.dst_to_src.remove(dst).iter().flat_map(|x| x.iter()) { + for src in srcs { + if let Some(dsts) = self + .src_to_dst + .get_mut(src) + .and_then(|rels_dsts| rels_dsts.get_mut(rel)) + { + dsts.remove(dst); + } + } + } + } + + pub fn has(&self, src: impl Into, rel: impl Into, dst: impl Into) -> bool { + let src = src.into(); + let rel = rel.into(); + let dst = dst.into(); + + self.src_to_dst + .get(&src) + .and_then(|rels_dsts| rels_dsts.get(&rel)) + .and_then(|dsts| dsts.get(&dst)) + .is_some() + } + + pub fn has_object<'a>(&self, obj: impl Into<&'a Object>) -> bool { + let obj = obj.into(); + let has_dst_obj = self.dst_to_src.contains_key(obj); + + let cursor = self + .src_to_dst + .lower_bound(Bound::Included(&ObjectOrSet::Object(obj.clone()))); + + let has_src_obj = if let Some(key) = cursor.key() { + obj.namespace == key.object().namespace && obj.id == key.object().id + } else { + false + }; + + has_dst_obj || has_src_obj + } + + pub fn has_recursive( + &self, + src: impl Into, + rel: impl Into, + dst: impl Into, + limit: u32, + ) -> bool { + let src = src.into(); + let rel = rel.into(); + let dst = dst.into(); + + let mut dist: HashMap<(Arc, Arc), u32> = HashMap::new(); + let mut q: BinaryHeap, Arc)>> = BinaryHeap::new(); + + for (nrel, ndst) in self + .src_to_dst + .get(&src) + .iter() + .flat_map(|x| x.iter()) + .flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone()))) + { + if *nrel == rel && *ndst == dst { + return true; + } + dist.insert((ndst.clone(), nrel.clone()), 1); + q.push(Distanced::one((ndst, nrel))); + } + + while let Some(distanced) = q.pop() { + let node_dist = distanced.distance() + 1; + let node = ObjectOrSet::Set(((*distanced.0).clone(), (*distanced.1).clone())); + for (nrel, ndst) in self + .src_to_dst + .get(&node) + .iter() + .flat_map(|x| x.iter()) + .flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone()))) + { + if *nrel == rel && *ndst == dst { + return true; + } + if let Some(existing_node_dist) = dist.get(&*distanced) { + if *existing_node_dist <= node_dist || node_dist >= limit { + continue; + } + } + dist.insert((ndst.clone(), nrel.clone()), node_dist); + q.push(Distanced::one((ndst, nrel))); + } + } + false + } + + pub async fn to_file(&self, file: &mut File) { + for (dst, rels_srcs) in self.dst_to_src.iter() { + file.write_all(format!("[{}:{}]\n", &dst.namespace, &dst.id).as_bytes()) + .await + .unwrap(); + for (rel, srcs) in rels_srcs.iter() { + let srcs = srcs + .iter() + .map(|src| { + let src_obj = src.object(); + let src_str = if src_obj.namespace == dst.namespace && src_obj.id == dst.id + { + "self".to_string() + } else { + format!("{}:{}", src_obj.namespace, src_obj.id) + }; + match &**src { + ObjectOrSet::Object(_) => src_str, + ObjectOrSet::Set(set) => { + format!("{}#{}", src_str, set.1 .0) + } + } + }) + .reduce(|acc, x| acc + ", " + &x) + .unwrap_or_default(); + + file.write_all(format!("{} = [{}]\n", &rel.0, &srcs).as_bytes()) + .await + .unwrap(); + } + file.write_all("\n".as_bytes()).await.unwrap(); + } + } + pub async fn from_file(file: &mut File) -> Self { + let reader = BufReader::new(file); + let mut lines = reader.lines(); + let mut graph = Self::new(); + let mut node: Option<(String, String)> = None; + while let Ok(Some(line)) = lines.next_line().await { + if line.starts_with('[') && line.ends_with(']') { + let line = &mut line[1..line.len() - 1].split(':'); + let namespace = line.next().unwrap(); + let id = line.next().unwrap(); + node = Some((namespace.to_string(), id.to_string())); + } else if line.contains('=') && line.contains('[') && line.contains(']') { + if let Some(dst) = &node { + let equals_pos = line.find('=').unwrap(); + let arr_start = line.find('[').unwrap(); + let arr_stop = line.find(']').unwrap(); + + let rel = line[..equals_pos].trim(); + let arr = line[arr_start + 1..arr_stop].split(", "); + + for obj in arr { + let src: ObjectOrSet = if obj.contains('#') { + let sep_1 = obj.find(':'); + let sep_2 = obj.find('#').unwrap(); + + let (namespace, id) = if let Some(sep_1) = sep_1 { + (&obj[..sep_1], &obj[sep_1 + 1..sep_2]) + } else { + (dst.0.as_str(), dst.1.as_str()) + }; + + let rel = &obj[sep_2 + 1..]; + + (namespace, id, rel).into() + } else { + let sep_1 = obj.find(':'); + + let (namespace, id) = if let Some(sep_1) = sep_1 { + (&obj[..sep_1], &obj[sep_1 + 1..]) + } else { + (dst.0.as_str(), dst.1.as_str()) + }; + (namespace, id).into() + }; + + graph.insert(src, rel, dst.clone()); + } + } + } + } + graph + } +} + +#[derive(PartialEq, Eq)] +struct Distanced { + distance: u32, + data: T, +} + +impl Distanced { + pub fn one(data: T) -> Self { + Self { distance: 1, data } + } + pub fn distance(&self) -> u32 { + self.distance + } +} + +impl Deref for Distanced { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl PartialOrd for Distanced { + fn partial_cmp(&self, other: &Self) -> Option { + self.distance.partial_cmp(&other.distance) + } +} +impl Ord for Distanced { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.distance.cmp(&other.distance) + } +} + +impl PartialOrd for Relation { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} +impl Ord for Relation { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for ObjectOrSet { + fn partial_cmp(&self, other: &Self) -> Option { + match ( + self.object().partial_cmp(other.object()), + self.relation(), + other.relation(), + ) { + (Some(Ordering::Equal), self_rel, other_rel) => self_rel.partial_cmp(&other_rel), + (ord, _, _) => ord, + } + } +} +impl Ord for ObjectOrSet { + fn cmp(&self, other: &Self) -> Ordering { + self.object() + .cmp(other.object()) + .then(self.relation().cmp(&other.relation())) + } +} + +impl PartialOrd for Object { + fn partial_cmp(&self, other: &Self) -> Option { + match self.namespace.partial_cmp(&other.namespace) { + Some(core::cmp::Ordering::Equal) => self.id.partial_cmp(&other.id), + ord => ord, + } + } +} +impl Ord for Object { + fn cmp(&self, other: &Self) -> Ordering { + self.namespace + .cmp(&other.namespace) + .then(self.id.cmp(&other.id)) + } +} + +impl From<(&str, &str)> for ObjectOrSet { + fn from((namespace, id): (&str, &str)) -> Self { + ObjectOrSet::Object(Object { + namespace: namespace.into(), + id: id.into(), + }) + } +} +impl From<(&str, &str, &str)> for ObjectOrSet { + fn from((namespace, id, rel): (&str, &str, &str)) -> Self { + ObjectOrSet::Set(((namespace, id).into(), Relation(rel.into()))) + } +} + +impl From<(&str, &str)> for Object { + fn from((namespace, id): (&str, &str)) -> Self { + Self { + namespace: namespace.into(), + id: id.into(), + } + } +} +impl From<(String, String)> for Object { + fn from((namespace, id): (String, String)) -> Self { + Self { + namespace: namespace.into(), + id: id.into(), + } + } +} + +impl From<&str> for Relation { + fn from(value: &str) -> Self { + Relation(value.into()) + } +} +impl From for Relation { + fn from(value: String) -> Self { + Relation(value.into()) + } +} + +impl ObjectOrSet { + pub fn object(&self) -> &Object { + match self { + ObjectOrSet::Object(obj) => obj, + ObjectOrSet::Set((obj, _)) => obj, + } + } + pub fn relation(&self) -> Option<&Relation> { + match self { + ObjectOrSet::Object(_) => None, + ObjectOrSet::Set((_, rel)) => Some(rel), + } + } +} +impl Relation { + pub fn new(relation: &str) -> Self { + Self(relation.into()) + } +}