use std::path::Path;
use std::{cmp, marker::PhantomData};
use libtugraph_sys::lgraph_api_graph_db_t;
use crate::{
field::{FieldData, FieldSpec},
raw::{RawGalaxy, RawGraphDB},
role_info::RoleInfo,
txn::{RoTxn, RwTxn},
types::{AccessLevel, EdgeUid},
user_info::UserInfo,
Result,
};
pub struct Graph<'gl> {
inner: RawGraphDB,
_marker: PhantomData<&'gl Galaxy>,
}
pub const MINIMUM_GRAPH_MAX_SIZE: usize = 1 << 20;
impl<'gl> Graph<'gl> {
pub unsafe fn from_ptr(ptr: *mut lgraph_api_graph_db_t) -> Graph<'gl> {
Graph {
inner: RawGraphDB::from_ptr(ptr),
_marker: PhantomData,
}
}
pub fn create_ro_txn(&self) -> Result<RoTxn<'_>> {
self.inner.create_read_txn().map(RoTxn::from_raw)
}
pub fn create_rw_txn(&self, optimistic: bool) -> Result<RwTxn<'_>> {
self.inner.create_write_txn(optimistic).map(RwTxn::from_raw)
}
#[deprecated = "transaction is not Send and cannot be used in Arc"]
pub fn fork_ro_txn(&self, txn: &RoTxn) -> Result<RoTxn<'_>> {
unsafe { self.inner.fork_txn(txn.as_raw()).map(RoTxn::from_raw) }
}
pub fn flush(&self) -> Result<()> {
self.inner.flush()
}
pub fn drop_all_data(&self) -> Result<()> {
self.inner.drop_all_data()
}
pub fn drop_all_vertex(&self) -> Result<()> {
self.inner.drop_all_vertex()
}
pub fn estimate_num_vertices(&self) -> Result<usize> {
self.inner.estimate_num_vertices()
}
pub fn add_vertex_label<'a, T>(
&self,
label: &str,
field_specs: T,
primary_field: &str,
) -> Result<bool>
where
T: IntoIterator<Item = &'a FieldSpec>,
{
let field_specs: Vec<_> = field_specs
.into_iter()
.map(|fs| fs.as_raw_field_spec())
.collect();
self.inner
.add_vertex_label(label, &field_specs, primary_field)
}
pub fn delete_vertex_label(&self, label: &str) -> Result<(bool, usize)> {
self.inner.delete_vertex_label(label)
}
pub fn alter_vertex_label_del_fields<'a, T>(
&self,
label: &str,
del_fields: T,
) -> Result<(bool, usize)>
where
T: IntoIterator<Item = &'a str>,
{
self.inner.alter_vertex_label_del_fields(label, del_fields)
}
pub fn alter_vertex_label_add_fields<'a, 'b, T, D>(
&self,
label: &str,
add_fields: T,
default_values: D,
) -> Result<(bool, usize)>
where
T: IntoIterator<Item = &'a FieldSpec>,
D: IntoIterator<Item = &'b FieldData>,
{
let add_fields: Vec<_> = add_fields
.into_iter()
.map(|fs| fs.as_raw_field_spec())
.collect();
let default_values: Vec<_> = default_values
.into_iter()
.map(|v| v.as_raw_field_data())
.collect();
self.inner
.alter_vertex_label_add_fields(label, &add_fields, &default_values)
}
pub fn alter_vertex_label_mod_fields<'a, T>(
&self,
label: &str,
mod_fields: T,
) -> Result<(bool, usize)>
where
T: IntoIterator<Item = &'a FieldSpec>,
{
let mod_fields: Vec<_> = mod_fields
.into_iter()
.map(|v| v.as_raw_field_spec())
.collect();
self.inner.alter_vertex_label_mod_fields(label, &mod_fields)
}
pub fn add_edge_label<'a, 'b, 'c, U, C>(
&self,
label: &str,
field_specs: U,
temporal_field: &str,
edge_constraints: C,
) -> Result<bool>
where
U: IntoIterator<Item = &'a FieldSpec>,
C: IntoIterator<Item = (&'b str, &'c str)>,
{
let field_specs: Vec<_> = field_specs
.into_iter()
.map(|v| v.as_raw_field_spec())
.collect();
self.inner
.add_edge_label(label, &field_specs, temporal_field, edge_constraints)
}
pub fn delete_edge_label(&self, label: &str) -> Result<(bool, usize)> {
self.inner.delete_edge_label(label)
}
pub fn alter_label_mod_edge_constraints<'a, 'b, 'c, U>(
&self,
label: &str,
constraints: U,
) -> Result<bool>
where
U: IntoIterator<Item = (&'b str, &'c str)>,
{
self.inner
.alter_label_mod_edge_constraints(label, constraints)
}
pub fn alter_edge_label_del_fields<'a, T>(
&self,
label: &str,
del_fields: T,
) -> Result<(bool, usize)>
where
T: IntoIterator<Item = &'a str>,
{
self.inner.alter_edge_label_del_fields(label, del_fields)
}
pub fn alter_edge_label_add_fields<'a, 'b, U, D>(
&self,
label: &str,
add_fields: U,
default_values: D,
) -> Result<(bool, usize)>
where
U: IntoIterator<Item = &'a FieldSpec>,
D: IntoIterator<Item = &'b FieldData>,
{
let add_fields: Vec<_> = add_fields
.into_iter()
.map(|fs| fs.as_raw_field_spec())
.collect();
let default_values: Vec<_> = default_values
.into_iter()
.map(|v| v.as_raw_field_data())
.collect();
self.inner
.alter_edge_label_add_fields(label, &add_fields, &default_values)
}
pub fn alter_edge_label_mod_fields<'a, T>(
&self,
label: &str,
mod_fields: T,
) -> Result<(bool, usize)>
where
T: IntoIterator<Item = &'a FieldSpec>,
{
let mod_fields: Vec<_> = mod_fields
.into_iter()
.map(|fs| fs.as_raw_field_spec())
.collect();
self.inner.alter_edge_label_mod_fields(label, &mod_fields)
}
pub fn add_vertex_index(&self, label: &str, field: &str, is_unique: bool) -> Result<bool> {
self.inner.add_vertex_index(label, field, is_unique)
}
pub fn add_edge_index(&self, label: &str, field: &str, is_unique: bool) -> Result<bool> {
self.inner.add_edge_index(label, field, is_unique)
}
pub fn is_vertex_indexed(&self, label: &str, field: &str) -> Result<bool> {
self.inner.is_vertex_indexed(label, field)
}
pub fn is_edge_indexed(&self, label: &str, field: &str) -> Result<bool> {
self.inner.is_edge_indexed(label, field)
}
pub fn delete_vertex_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.delete_vertex_index(label, field)
}
pub fn delete_edge_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.delete_edge_index(label, field)
}
pub fn get_description(&self) -> Result<String> {
self.inner.get_description()
}
pub fn get_max_size(&self) -> Result<usize> {
self.inner.get_max_size()
}
pub fn add_vertex_full_text_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.add_vertex_full_text_index(label, field)
}
pub fn add_edge_full_text_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.add_edge_full_text_index(label, field)
}
pub fn delete_vertex_full_text_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.delete_vertex_full_text_index(label, field)
}
pub fn delete_edge_full_text_index(&self, label: &str, field: &str) -> Result<bool> {
self.inner.delete_edge_full_text_index(label, field)
}
pub fn rebuild_full_text_index<'a, 'b, V, E>(
&self,
vertex_labels: V,
edge_labels: E,
) -> Result<()>
where
V: IntoIterator<Item = &'a str>,
E: IntoIterator<Item = &'b str>,
{
self.inner
.rebuild_full_text_index(vertex_labels, edge_labels)
}
pub fn list_full_text_indexes(&self) -> Result<Vec<ListIndex>> {
self.inner.list_full_text_indexes().map(|v| {
v.into_iter()
.map(|(is_vertex, label_name, property_name)| ListIndex {
is_vertex,
label_name,
field_name: property_name,
})
.collect()
})
}
pub fn query_vertex_by_full_text_index(
&self,
label: &str,
query: &str,
topn: i32,
) -> Result<Vec<QueryVertexFTIndex>> {
self.inner
.query_vertex_by_full_text_index(label, query, topn)
.map(|v| {
v.into_iter()
.map(|(vid, score)| QueryVertexFTIndex { vid, score })
.collect()
})
}
pub fn query_edge_by_full_text_index(
&self,
label: &str,
query: &str,
topn: i32,
) -> Result<Vec<QueryEdgeFTIndex>> {
self.inner
.query_edge_by_full_text_index(label, query, topn)
.map(|v| {
v.into_iter()
.map(|(raw, score)| QueryEdgeFTIndex {
euid: EdgeUid::from_raw(&raw),
score,
})
.collect()
})
}
}
#[derive(Default)]
pub struct OpenOptions {
durable: bool,
create: bool,
}
impl OpenOptions {
pub fn new() -> Self {
OpenOptions {
durable: false,
create: false,
}
}
pub fn durable(mut self, durable: bool) -> Self {
self.durable = durable;
self
}
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
pub fn open<P: AsRef<Path>>(self, dir: P, username: &str, password: &str) -> Result<Galaxy> {
RawGalaxy::new_with_user(dir.as_ref(), username, password, self.durable, self.create)
.map(|raw| Galaxy { inner: raw })
}
}
unsafe impl Sync for Graph<'_> {}
pub struct ListGraph {
pub name: String,
pub desc: String,
pub max_size: usize,
}
pub struct ListIndex {
pub is_vertex: bool,
pub label_name: String,
pub field_name: String,
}
pub struct QueryVertexFTIndex {
pub vid: i64,
pub score: f32,
}
pub struct QueryEdgeFTIndex {
pub euid: EdgeUid,
pub score: f32,
}
pub struct GraphAccess {
pub name: String,
pub access_level: AccessLevel,
}
pub struct ListUser {
pub name: String,
pub info: UserInfo,
}
pub struct ListRole {
pub name: String,
pub info: RoleInfo,
}
pub struct ModGraphOptions<'gl> {
desc: Option<String>,
max_size: Option<usize>,
galaxy: &'gl Galaxy,
}
impl<'gl> ModGraphOptions<'gl> {
pub fn mod_desc(mut self, new_desc: String) -> Self {
self.desc = Some(new_desc);
self
}
pub fn mod_max_size(mut self, new_max_size: usize) -> Self {
self.max_size = Some(new_max_size);
self
}
pub fn apply(self, graph: &str) -> Result<bool> {
self.galaxy._mod_graph(
graph,
self.desc.is_some(),
&self.desc.unwrap_or_default(),
self.max_size.is_some(),
self.max_size.unwrap_or_default(),
)
}
}
pub struct Galaxy {
inner: RawGalaxy,
}
impl Galaxy {
pub fn open<P: AsRef<Path>>(dir: P, username: &str, password: &str) -> Result<Galaxy> {
OpenOptions::new().open(dir, username, password)
}
pub fn set_current_user(&self, user: &str, password: &str) -> Result<()> {
self.inner.set_current_user(user, password)
}
pub fn set_user_without_auth(&self, user: &str) -> Result<()> {
self.inner.set_user(user)
}
pub fn create_graph(&self, name: &str, desc: &str, max_size: usize) -> Result<bool> {
self.inner
.create_graph(name, desc, cmp::max(max_size, MINIMUM_GRAPH_MAX_SIZE))
}
pub fn delete_graph(&self, graph: &str) -> Result<bool> {
self.inner.delete_graph(graph)
}
fn _mod_graph(
&self,
graph: &str,
mod_desc: bool,
desc: &str,
mod_size: bool,
new_max_size: usize,
) -> Result<bool> {
self.inner
.mod_graph(graph, mod_desc, desc, mod_size, new_max_size)
}
pub fn mod_graph(&self) -> ModGraphOptions<'_> {
ModGraphOptions {
desc: None,
max_size: None,
galaxy: self,
}
}
pub fn list_graphs(&self) -> Result<Vec<ListGraph>> {
self.inner.list_graphs().map(|g| {
g.into_iter()
.map(|(name, (desc, max_size))| ListGraph {
name,
desc,
max_size,
})
.collect()
})
}
pub fn create_user(&self, username: &str, password: &str, desc: &str) -> Result<bool> {
self.inner.create_user(username, password, desc)
}
pub fn delete_user(&self, username: &str) -> Result<bool> {
self.inner.delete_user(username)
}
pub fn set_password(
&self,
username: &str,
old_password: &str,
new_password: &str,
) -> Result<bool> {
self.inner
.set_password(username, old_password, new_password)
}
pub fn set_user_desc(&self, username: &str, desc: &str) -> Result<bool> {
self.inner.set_user_desc(username, desc)
}
pub fn set_user_roles<'a, R>(&self, username: &str, roles: R) -> Result<bool>
where
R: IntoIterator<Item = &'a str>,
{
self.inner.set_user_roles(username, roles)
}
pub fn set_user_graph_access(
&self,
username: &str,
graph: &str,
access: AccessLevel,
) -> Result<bool> {
self.inner.set_user_graph_access(username, graph, access)
}
pub fn disable_user(&self, username: &str) -> Result<bool> {
self.inner.disable_user(username)
}
pub fn enable_user(&self, username: &str) -> Result<bool> {
self.inner.enable_user(username)
}
pub fn list_users(&self) -> Result<Vec<ListUser>> {
self.inner.list_users().map(|v| {
v.into_iter()
.map(|(name, info)| ListUser {
name,
info: UserInfo::from_raw(&info),
})
.collect()
})
}
pub fn get_user_info(&self, username: &str) -> Result<UserInfo> {
self.inner
.get_user_info(username)
.map(|info| UserInfo::from_raw(&info))
}
pub fn create_role(&self, role: &str, desc: &str) -> Result<bool> {
self.inner.create_role(role, desc)
}
pub fn delete_role(&self, role: &str) -> Result<bool> {
self.inner.delete_role(role)
}
pub fn disable_role(&self, role: &str) -> Result<bool> {
self.inner.disable_role(role)
}
pub fn enable_role(&self, role: &str) -> Result<bool> {
self.inner.enable_role(role)
}
pub fn set_role_desc(&self, role: &str, desc: &str) -> Result<bool> {
self.inner.set_role_desc(role, desc)
}
pub fn set_role_access_rights<'a, T>(&self, role: &str, graph_access: T) -> Result<bool>
where
T: IntoIterator<Item = &'a GraphAccess>,
{
self.inner.set_role_access_rights(
role,
graph_access
.into_iter()
.map(|ga| (ga.name.as_str(), ga.access_level)),
)
}
pub fn set_role_access_rights_incremental<'a, T>(
&self,
role: &str,
graph_access: T,
) -> Result<bool>
where
T: IntoIterator<Item = &'a GraphAccess>,
{
self.inner.set_role_access_rights_incremental(
role,
graph_access
.into_iter()
.map(|ga| (ga.name.as_str(), ga.access_level)),
)
}
pub fn get_role_info(&self, role: &str) -> Result<RoleInfo> {
self.inner
.get_role_info(role)
.map(|info| RoleInfo::from_raw(&info))
}
pub fn list_roles(&self) -> Result<Vec<ListRole>> {
self.inner.list_roles().map(|v| {
v.into_iter()
.map(|(name, info)| ListRole {
name,
info: RoleInfo::from_raw(&info),
})
.collect()
})
}
pub fn get_access_level(&self, username: &str, graph: &str) -> Result<AccessLevel> {
self.inner.get_access_level(username, graph)
}
pub fn open_graph(&self, graph: &str, read_only: bool) -> Result<Graph<'_>> {
unsafe {
self.inner.open_graph(graph, read_only).map(|raw| Graph {
inner: raw,
_marker: PhantomData,
})
}
}
}
unsafe impl Send for Galaxy {}
unsafe impl Sync for Galaxy {}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
#[test]
fn test_mod_graph() {
let tmpdir = tempfile::tempdir().unwrap();
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let graph = galaxy.open_graph("default", true).unwrap();
let new_desc = "The new description of default graph";
let modified = galaxy
.mod_graph()
.mod_desc(new_desc.into())
.apply("default")
.unwrap();
assert!(modified);
assert_eq!(graph.get_description(), Ok(new_desc.into()));
}
#[test]
fn test_admin_switch_user() {
let tmpdir = tempfile::tempdir().unwrap();
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let mut usernames: Vec<_> = galaxy
.list_users()
.unwrap()
.into_iter()
.map(|u| u.name)
.collect();
usernames.sort();
assert_eq!(usernames, vec!["admin", "test_user1"]);
galaxy
.set_user_without_auth("test_user1")
.expect("admin should be permitted to switch to test_user1");
galaxy
.set_user_without_auth("admin")
.expect("switching back from test_user1 to admin should be ok");
}
#[test]
fn test_normal_user_switch_user() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user two")
.unwrap();
assert!(created);
let mut usernames: Vec<_> = galaxy
.list_users()
.unwrap()
.into_iter()
.map(|u| u.name)
.collect();
usernames.sort();
assert_eq!(usernames, vec!["admin", "test_user1", "test_user2"]);
}
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "test_user1", "test_password1")
.unwrap();
galaxy
.set_current_user("test_user2", "test_password2")
.expect("switching to test_user2 with password should be ok");
galaxy
.set_current_user("test_user1", "wrong_password")
.expect_err("switching back to test_user1 with wrong password should be err");
}
#[test]
fn test_create_user() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
}
let galaxy = Galaxy::open(tmpdir.path(), "test_user1", "test_password1").unwrap();
galaxy
.create_user("test_user2", "test_password2", "user two")
.expect_err("normal user cannot create new user");
}
#[test]
fn test_delete_user() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user1", "test_password1").unwrap();
galaxy
.delete_user("test_user1")
.expect_err("normal user cannot delete user");
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
galaxy
.delete_user("test_user1")
.expect("admin can delete user");
}
}
#[test]
fn test_set_password() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user one")
.unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
galaxy
.set_password("test_user1", "test_password1", "new_password1")
.expect("admin can set other users' password");
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user1", "new_password1").unwrap();
galaxy
.set_password("test_user2", "test_password2", "new_password2")
.expect_err("normal user cannot set other user's password");
galaxy
.set_password("test_user1", "new_password1", "test_password1")
.expect("normal user can set itself password");
}
}
#[test]
fn test_set_user_desc() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user one")
.unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
let setted = galaxy
.set_user_desc("test_user1", "new_description1")
.unwrap();
assert!(setted);
let desc = galaxy.get_user_info("test_user1").unwrap().desc;
assert_eq!(desc, "new_description1");
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user1", "test_password1").unwrap();
galaxy
.set_user_desc("test_user2", "new_description2")
.expect_err("normal user cannot set other user's description");
let setted = galaxy
.set_user_desc("test_user1", "the newest description1")
.unwrap();
assert!(setted);
let desc = galaxy.get_user_info("test_user1").unwrap().desc;
assert_eq!(desc, "the newest description1");
}
}
#[test]
fn test_set_user_roles() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user one")
.unwrap();
assert!(created);
let created = galaxy.create_role("operator", "a operator role").unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
let setted = galaxy.set_user_roles("test_user1", ["operator"]).unwrap();
assert!(setted);
let roles = galaxy.get_user_info("test_user1").unwrap().roles;
assert_eq!(
roles,
HashSet::from_iter(["test_user1".to_string(), "operator".to_string()])
);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user2", "test_password2").unwrap();
galaxy
.set_user_roles("test_user1", ["operator"])
.expect_err("normal user cannot set other user's roles");
galaxy
.set_user_roles("test_user2", ["operator"])
.expect_err("normal user cannot set itself roles");
}
}
#[test]
fn test_set_user_graph_access() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user one")
.unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
let setted = galaxy
.set_user_graph_access("test_user1", "default", AccessLevel::Read)
.unwrap();
assert!(setted);
let al = galaxy.get_access_level("test_user1", "default").unwrap();
assert!(matches!(al, AccessLevel::Read));
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user2", "test_password2").unwrap();
galaxy
.set_user_graph_access("test_user1", "default", AccessLevel::Write)
.expect_err("normal user cannot set other user's access level");
galaxy
.set_user_graph_access("test_user2", "default", AccessLevel::Full)
.expect_err("normal user cannot set itself roles");
}
}
#[test]
fn test_enable_disable_user() {
let tmpdir = tempfile::tempdir().unwrap();
{
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy
.create_user("test_user2", "test_password2", "user one")
.unwrap();
assert!(created);
}
{
let galaxy = Galaxy::open(tmpdir.path(), "test_user1", "test_password1").unwrap();
galaxy
.disable_user("test_user1")
.expect_err("user cannot disable itself");
galaxy
.disable_user("test_user2")
.expect_err("normal user cannot disable others");
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
let disabled = galaxy.disable_user("test_user1").unwrap();
assert!(disabled);
}
{
assert!(Galaxy::open(tmpdir.path(), "test_user1", "test_password1").is_err());
}
{
let galaxy = Galaxy::open(tmpdir.path(), "admin", "73@TuGraph").unwrap();
galaxy
.enable_user("test_user1")
.expect("enable user should be ok");
}
{
assert!(Galaxy::open(tmpdir.path(), "test_user1", "test_password1").is_ok());
}
}
#[test]
fn test_role_operations() {
let tmpdir = tempfile::tempdir().unwrap();
let galaxy = OpenOptions::new()
.create(true)
.open(tmpdir.path(), "admin", "73@TuGraph")
.unwrap();
galaxy
.create_graph("test_graph1", "test graph one", MINIMUM_GRAPH_MAX_SIZE)
.expect("create graph should be ok");
let created = galaxy
.create_user("test_user1", "test_password1", "user one")
.unwrap();
assert!(created);
let created = galaxy.create_role("operator", "a good operator").unwrap();
assert!(created);
let created = galaxy.create_role("developer", "C++ programmer").unwrap();
assert!(created);
let roles: Vec<_> = galaxy.list_roles().unwrap();
assert_eq!(
roles
.iter()
.map(|r| r.name.as_str())
.collect::<HashSet<_>>(),
HashSet::from(["admin", "test_user1", "operator", "developer"]),
);
assert!(roles.iter().all(|r| !r.info.disabled));
galaxy
.disable_role("operator")
.expect("disable operator should be ok");
let operator = galaxy.get_role_info("operator").unwrap();
assert!(operator.disabled);
galaxy
.enable_role("operator")
.expect("enable operator should be ok");
let operator = galaxy.get_role_info("operator").unwrap();
assert!(!operator.disabled);
galaxy
.set_role_desc("operator", "a new operator")
.expect("set role description should be ok");
let operator = galaxy.get_role_info("operator").unwrap();
assert_eq!(operator.desc, "a new operator");
galaxy
.set_role_access_rights(
"operator",
&[GraphAccess {
name: "default".to_string(),
access_level: AccessLevel::Full,
}],
)
.expect("set role access rights should be ok");
let operator = galaxy.get_role_info("operator").unwrap();
assert!(matches!(
operator.graph_access.get("default").unwrap(),
AccessLevel::Full
));
galaxy
.set_role_access_rights_incremental(
"developer",
&[GraphAccess {
name: "default".to_string(),
access_level: AccessLevel::Read,
}],
)
.expect("set role access rights incrementally should be ok");
let developer = galaxy.get_role_info("developer").unwrap();
assert!(matches!(
developer.graph_access.get("default").unwrap(),
AccessLevel::Read
));
galaxy
.set_role_access_rights_incremental(
"developer",
&[GraphAccess {
name: "test_graph1".to_string(),
access_level: AccessLevel::Write,
}],
)
.expect("set role access rights incrementally should be ok");
let developer = galaxy.get_role_info("developer").unwrap();
assert!(matches!(
developer.graph_access.get("default").unwrap(),
AccessLevel::Read
));
assert!(matches!(
developer.graph_access.get("test_graph1").unwrap(),
AccessLevel::Write
));
galaxy
.delete_role("developer")
.expect("delete a role should be ok");
let roles: Vec<_> = galaxy.list_roles().unwrap();
assert_eq!(
roles
.iter()
.map(|r| r.name.as_str())
.collect::<HashSet<_>>(),
HashSet::from(["admin", "test_user1", "operator"]),
);
}
}