Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

With from bolt type #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "neo4rs"
version = "0.6.0"
version = "0.6.1"
authors = ["Neo4j Labs <[email protected]>", "John Pradeep Vincent <[email protected]>"]
edition = "2021"
description = "Neo4j driver in rust"
Expand Down
18 changes: 16 additions & 2 deletions lib/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,22 @@ impl Connection {
};

match url.scheme() {
"bolt" | "neo4j" | "" => Self::new_unencrypted(stream, user, password).await,
"bolt+s" | "neo4j+s" => Self::new_tls(stream, host, user, password).await,
"bolt" | "" => Self::new_unencrypted(stream, user, password).await,
"bolt+s" => Self::new_tls(stream, host, user, password).await,
"neo4j" => {
log::warn!(concat!(
"This driver does not yet implement client-side routing. ",
"It is possible that operations against a cluster (such as Aura) will fail."
));
Self::new_unencrypted(stream, user, password).await
}
"neo4j+s" => {
log::warn!(concat!(
"This driver does not yet implement client-side routing. ",
"It is possible that operations against a cluster (such as Aura) will fail."
));
Self::new_tls(stream, host, user, password).await
}
otherwise => Err(Error::UnsupportedScheme(otherwise.to_owned())),
}
}
Expand Down
19 changes: 11 additions & 8 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,11 +764,14 @@ mod txn;
mod types;
mod version;

pub use crate::config::{Config, ConfigBuilder};
pub use crate::errors::*;
pub use crate::graph::{query, Graph};
pub use crate::query::Query;
pub use crate::row::{Node, Path, Point2D, Point3D, Relation, Row, UnboundedRelation};
pub use crate::stream::RowStream;
pub use crate::txn::Txn;
pub use crate::version::Version;
pub use config::{Config, ConfigBuilder};
pub use errors::*;
pub use graph::{query, Graph};
pub use query::Query;
pub use row::{Node, Path, Point2D, Point3D, Relation, Row, UnboundedRelation};
pub use stream::RowStream;
pub use txn::Txn;
pub use version::Version;

pub use types::*;
pub use neo4rs_macros::FromBoltType;
7 changes: 7 additions & 0 deletions lib/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ impl Node {
self.inner.labels.iter().map(|l| l.to_string()).collect()
}

pub fn props<T>(&self) -> Result<T, T::Error>
where
T: std::convert::TryFrom<BoltMap>,
{
self.inner.properties.clone().try_into()
}

/// Get the attributes of the node
pub fn get<T: std::convert::TryFrom<BoltType>>(&self, key: &str) -> Option<T> {
self.inner.get(key)
Expand Down
94 changes: 94 additions & 0 deletions lib/tests/into_custom_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use neo4rs::*;

mod container;

#[derive(Debug, FromBoltType, PartialEq)]
struct Post {
title: String,
body: Option<String>,
}

#[derive(Debug, FromBoltType, PartialEq)]
struct Unit;

#[tokio::test]
async fn from_row_for_custom_struct() {
let config = ConfigBuilder::default()
.db("neo4j")
.fetch_size(500)
.max_connections(10);
let neo4j = container::Neo4jContainer::from_config(config).await;
let graph = neo4j.graph();

let mut result = graph
.execute(
query("RETURN { title: $title, body: $body } as n")
.params([("title", "Hello"), ("body", "World!")]),
)
.await
.unwrap();
let row = result.next().await.unwrap().unwrap();
let value: Post = row.get("n").unwrap();
assert_eq!(
Post {
title: "Hello".into(),
body: Some("World!".into()),
},
value
);
assert!(result.next().await.unwrap().is_none());

let mut result = graph
.execute(
query("RETURN { title: $title } as n")
.params([("title", "With empty body")]),
)
.await
.unwrap();
let row = result.next().await.unwrap().unwrap();
let value: Post = row.get("n").unwrap();
assert_eq!(
Post {
title: "With empty body".into(),
body: None,
},
value
);
assert!(result.next().await.unwrap().is_none());
}

#[tokio::test]
async fn from_node_props_for_custom_struct() {
let config = ConfigBuilder::default()
.db("neo4j")
.fetch_size(500)
.max_connections(10);
let neo4j = container::Neo4jContainer::from_config(config).await;
let graph = neo4j.graph();

let mut result = graph
.execute(
query("CREATE (n:Post { title: $title, body: $body }) RETURN n")
.params([("title", "Hello"), ("body", "World!")]),
)
.await
.unwrap();

while let Ok(Some(row)) = result.next().await {
let node: Node = row.get("n").unwrap();
let id = node.id();
let labels = node.labels();

let value: Post = node.props().unwrap();

assert_eq!(
Post {
title: "Hello".into(),
body: Some("World!".into()),
},
value
);
assert_eq!(labels, vec!["Post"]);
assert!(id >= 0);
}
}
80 changes: 80 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,86 @@ use quote::quote;
use syn::parse_macro_input;
use syn::{DeriveInput, MetaList};

fn is_option(ty: &syn::Type) -> bool {
if let syn::Type::Path(syn::TypePath { qself: None, path }) = ty {
let segments_str = &path
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>()
.join(":");
["Option", "std:option:Option", "core:option:Option"]
.iter()
.find(|s| segments_str == *s)
.is_some()
} else {
false
}
}

#[proc_macro_derive(FromBoltType)]
pub fn from_bolt_type(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;

let fields = if let syn::Data::Struct(structure) = ast.data {
match structure.fields {
syn::Fields::Named(syn::FieldsNamed { named, .. }) => named,
syn::Fields::Unnamed(_) => {
unimplemented!(concat!(stringify!(#name), ": unnamed fields not supported"))
}
syn::Fields::Unit => syn::punctuated::Punctuated::new(),
}
} else {
unimplemented!(concat!(stringify!(#name), ": not a struct"));
};

let from_map_fields = fields.iter().map(|f| {
let name = &f.ident;
let ty = &f.ty;

if is_option(ty) {
quote! {
#name: value.get(stringify!(#name))
}
} else {
quote! {
#name: value.get(stringify!(#name)).unwrap()
}
}
});

let expanded = quote!(
impl From<BoltMap> for #struct_name {
fn from(value: BoltMap) -> Self {
Self {
#(#from_map_fields,)*
}
}
}

impl From<BoltNode> for #struct_name {
fn from(value: BoltNode) -> Self {
let value = value.properties;

value.into()
}
}

impl From<BoltType> for #struct_name {
fn from(value: BoltType) -> Self {
match value {
BoltType::Map(inner) => inner.into(),
BoltType::Node(inner) => inner.into(),
v => panic!("{}: can not be made from {v:?}", stringify!(#struct_name))
}
}
}
);

expanded.into()
}

#[proc_macro_derive(BoltStruct, attributes(signature))]
pub fn derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
Expand Down