From 6fedc613434ee8fe4c3781f06bc670e56de5de1e Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Sat, 12 Aug 2023 16:23:25 -0400 Subject: [PATCH 01/24] remove improper categories --- lib/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index d250766..5df4b89 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ version = "0.2.0" edition = "2021" license = "MIT" keywords = ["neo4j", "cypher", "dto", "query", "graph"] -categories = ["database", "data-structures", "encoding"] +categories = ["database"] repository = "https://github.com/jifalops/cypher-dto" [features] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 4d08df5..0273245 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -5,7 +5,7 @@ version = "0.2.0" edition = "2021" license = "MIT" keywords = ["neo4j", "cypher", "dto", "query", "graph"] -categories = ["database", "data-structures", "encoding"] +categories = ["database"] repository = "https://github.com/jifalops/cypher-dto" [lib] From 854d727a71a8141964153eabd883aebef3c0b911 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 21:56:57 -0400 Subject: [PATCH 02/24] make the generated builder infallible, but lose new/default. --- macros/src/derive/entity/builder.rs | 53 +++++++++-------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/macros/src/derive/entity/builder.rs b/macros/src/derive/entity/builder.rs index a9e7118..2a10460 100644 --- a/macros/src/derive/entity/builder.rs +++ b/macros/src/derive/entity/builder.rs @@ -1,14 +1,14 @@ use super::{ArgHelper, Entity}; use quote::{__private::TokenStream, format_ident, quote}; -use syn::Type; + pub fn impl_builder(entity: &Entity) -> TokenStream { let entity_ident = entity.ident(); - let entity_name = entity.name(); + let _entity_name = entity.name(); let ident = format_ident!("{}Builder", entity_ident); let (idents, types, names, comments, _into_params, _from_boltmaps) = entity.fields.to_vectors(); - let mut opt_types = Vec::new(); + let mut all_types = Vec::new(); let ( arg_type, arg_into_field_suffix, @@ -23,29 +23,21 @@ pub fn impl_builder(entity: &Entity) -> TokenStream { for index in 0..idents.len() { let id = idents[index]; - let name = names[index]; + let _name = names[index]; let ty = types[index]; let arg_convert = &arg_into_field_suffix[index]; match ty.is_option() { true => { - opt_types.push(ty.as_type().clone()); - assignments.push(quote!(#id)); + all_types.push(ty.as_type().clone()); + assignments.push(quote!(#id #arg_convert)); from_entity.push(quote!(value.#id)); into_entity.push(quote!(value.#id)); } false => { - let t = ty.as_type(); - let s = format!("Option<{}>", quote!(#t)); - opt_types.push(match syn::parse_str::(&s) { - Ok(ty) => ty, - Err(e) => panic!( - "Failed to wrap type in Option for {}: {}. ({})", - ident, e, s - ), - }); - assignments.push(quote!(Some(#id #arg_convert))); - from_entity.push(quote!(Some(value.#id))); - into_entity.push(quote!(value.#id.ok_or(::cypher_dto::Error::BuilderError(#entity_name.to_owned(), #name.to_owned()))?)); + all_types.push(ty.as_type().clone()); + assignments.push(quote!(#id #arg_convert)); + from_entity.push(quote!(value.#id)); + into_entity.push(quote!(value.#id)); } } } @@ -53,14 +45,9 @@ pub fn impl_builder(entity: &Entity) -> TokenStream { quote! { #vis struct #ident { - #( #idents: #opt_types, )* + #( #idents: #all_types, )* } impl #ident { - pub fn new() -> Self { - Self { - #( #idents: None, )* - } - } #( #( #comments )* pub fn #idents(mut self, #idents: #arg_type) -> Self { @@ -68,13 +55,8 @@ pub fn impl_builder(entity: &Entity) -> TokenStream { self } )* - pub fn build(self) -> ::std::result::Result<#entity_ident, ::cypher_dto::Error> { - self.try_into() - } - } - impl Default for #ident { - fn default() -> Self { - Self::new() + pub fn build(self) -> #entity_ident { + self.into() } } impl From<#entity_ident> for #ident { @@ -84,12 +66,11 @@ pub fn impl_builder(entity: &Entity) -> TokenStream { } } } - impl TryFrom<#ident> for #entity_ident { - type Error = ::cypher_dto::Error; - fn try_from(value: #ident) -> ::std::result::Result { - Ok(Self { + impl From<#ident> for #entity_ident { + fn from(value: #ident) -> Self { + Self { #( #idents: #into_entity, )* - }) + } } } impl #entity_ident { From 4d8c32ae1b25ac8fb99dc35bfa49c6d2a09819ca Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 21:58:45 -0400 Subject: [PATCH 03/24] remove use/import of traits. --- macros/src/derive/entity.rs | 6 +----- macros/src/derive/node.rs | 2 -- macros/src/derive/relation.rs | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/macros/src/derive/entity.rs b/macros/src/derive/entity.rs index 7e64e9e..b40a49e 100644 --- a/macros/src/derive/entity.rs +++ b/macros/src/derive/entity.rs @@ -66,15 +66,11 @@ impl Entity { let new_and_getters = new_and_getters::impl_new_and_getters(self); quote! { - use ::cypher_dto::Entity as _; - use ::cypher_dto::QueryFields as _; - impl ::cypher_dto::Entity for #struct_ident { fn typename() -> &'static str { #struct_name } - } - impl ::cypher_dto::QueryFields for #struct_ident { + fn field_names() -> &'static [&'static str] { &[#(#names),*] } diff --git a/macros/src/derive/node.rs b/macros/src/derive/node.rs index c2cdc89..8e0b3dc 100644 --- a/macros/src/derive/node.rs +++ b/macros/src/derive/node.rs @@ -34,7 +34,6 @@ impl Node { let builder_impl = self.inner.builder_impl(); quote! { #entity_impl - use ::cypher_dto::NodeEntity as _; impl ::cypher_dto::NodeEntity for #main_ident { type Id = #id_ident; fn identifier(&self) -> Self::Id { @@ -72,7 +71,6 @@ impl Node { #( #idents: #types, )* } #entity_impl - use ::cypher_dto::NodeId as _; impl ::cypher_dto::NodeId for #id_ident { type T = #main_ident; } diff --git a/macros/src/derive/relation.rs b/macros/src/derive/relation.rs index 1f8056d..56e6ce8 100644 --- a/macros/src/derive/relation.rs +++ b/macros/src/derive/relation.rs @@ -46,7 +46,6 @@ impl Relation { let builder_impl = self.inner.builder_impl(); quote! { #entity_impl - use ::cypher_dto::RelationEntity as _; impl ::cypher_dto::RelationEntity for #main_ident { type Id = #id_ident; fn identifier(&self) -> Self::Id { @@ -101,7 +100,6 @@ impl Relation { #( #idents: #types, )* } #entity_impl - use ::cypher_dto::RelationId as _; impl ::cypher_dto::RelationId for #id_ident { type T = #main_ident; } From a57d7b03f39a0312d39539013b37b13f479844cb Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 22:00:56 -0400 Subject: [PATCH 04/24] absorb QueryFields into Entity (fewer use statements for users) --- lib/src/entity.rs | 35 +++++++++++--------------- lib/src/lib.rs | 2 +- lib/src/relationship.rs | 2 +- lib/tests/common/fixtures/company.rs | 10 +++----- lib/tests/common/fixtures/person.rs | 10 +++----- lib/tests/common/fixtures/worked_at.rs | 10 +++----- lib/tests/common/fixtures/works_at.rs | 8 +++--- 7 files changed, 28 insertions(+), 49 deletions(-) diff --git a/lib/src/entity.rs b/lib/src/entity.rs index b5e913c..c6e2b0f 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -2,22 +2,10 @@ use crate::{format_query_fields, Stamps}; use neo4rs::{Query, Row}; /// A named collection of [QueryFields], such as a node or relationship. -pub trait Entity: QueryFields { +pub trait Entity: TryFrom { /// The primary label for a node, or the type of a relationship. fn typename() -> &'static str; - /// Formatted like `typename() { as_query_fields() }`, or for a fieldless relationship, just `typename()`. - fn as_query_obj(prefix: Option<&str>, mode: StampMode) -> String { - let fields = Self::as_query_fields(prefix, mode); - if fields.is_empty() { - return Self::typename().to_owned(); - } - format!("{} {{ {} }}", Self::typename(), fields) - } -} - -/// A collection of fields to be used in a cypher query. -pub trait QueryFields: TryFrom { /// The fields in this set. fn field_names() -> &'static [&'static str]; @@ -46,6 +34,15 @@ pub trait QueryFields: TryFrom { /// Adds all field values to the query parameters, matching placeholders in [as_query_fields]. fn add_values_to_params(&self, query: Query, prefix: Option<&str>, mode: StampMode) -> Query; + + /// Formatted like `typename() { as_query_fields() }`, or for a fieldless relationship, just `typename()`. + fn as_query_obj(prefix: Option<&str>, mode: StampMode) -> String { + let fields = Self::as_query_fields(prefix, mode); + if fields.is_empty() { + return Self::typename().to_owned(); + } + format!("{} {{ {} }}", Self::typename(), fields) + } } /// Controls which timestamps are hardcoded in a query (e.g. `datetime()`), @@ -113,8 +110,7 @@ pub(crate) mod tests { fn typename() -> &'static str { "Foo" } - } - impl QueryFields for Foo { + fn field_names() -> &'static [&'static str] { &["name", "age"] } @@ -149,8 +145,7 @@ pub(crate) mod tests { fn typename() -> &'static str { "Bar" } - } - impl QueryFields for Bar { + fn field_names() -> &'static [&'static str] { &["created", "updated"] } @@ -197,8 +192,7 @@ pub(crate) mod tests { fn typename() -> &'static str { "BAZ" } - } - impl QueryFields for Baz { + fn field_names() -> &'static [&'static str] { &[] } @@ -426,8 +420,7 @@ pub(crate) mod tests { fn typename() -> &'static str { "NumTypes" } - } - impl QueryFields for NumTypes { + fn field_names() -> &'static [&'static str] { &[ "usize_num", diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 0fbc621..068aeb6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -30,7 +30,7 @@ mod stamps; #[cfg(feature = "macros")] pub use cypher_dto_macros::*; -pub use entity::{Entity, QueryFields, StampMode}; +pub use entity::{Entity, StampMode}; pub use error::Error; pub use format::{format_param, format_query_fields}; pub use node::{NodeEntity, NodeId}; diff --git a/lib/src/relationship.rs b/lib/src/relationship.rs index 077f461..9669aab 100644 --- a/lib/src/relationship.rs +++ b/lib/src/relationship.rs @@ -1,4 +1,4 @@ -use crate::{Entity, NodeEntity, NodeId, QueryFields, StampMode}; +use crate::{Entity, NodeEntity, NodeId, StampMode}; use neo4rs::{Query, Relation, UnboundedRelation}; /// A relationship [Entity]. diff --git a/lib/tests/common/fixtures/company.rs b/lib/tests/common/fixtures/company.rs index 81f94b8..7365ed9 100644 --- a/lib/tests/common/fixtures/company.rs +++ b/lib/tests/common/fixtures/company.rs @@ -1,7 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{ - format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, QueryFields, StampMode, -}; +use cypher_dto::{format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, StampMode}; use neo4rs::{Node, Query, Row}; /// Has a multi-valued ID and required timestamps. @@ -16,8 +14,7 @@ impl Entity for Company { fn typename() -> &'static str { "Company" } -} -impl QueryFields for Company { + fn field_names() -> &'static [&'static str] { &["name", "state", "created", "updated"] } @@ -121,8 +118,7 @@ impl Entity for CompanyId { fn typename() -> &'static str { Company::typename() } -} -impl QueryFields for CompanyId { + fn field_names() -> &'static [&'static str] { &["name", "state"] } diff --git a/lib/tests/common/fixtures/person.rs b/lib/tests/common/fixtures/person.rs index 9b84961..612154a 100644 --- a/lib/tests/common/fixtures/person.rs +++ b/lib/tests/common/fixtures/person.rs @@ -1,7 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{ - format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, QueryFields, StampMode, -}; +use cypher_dto::{format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, StampMode}; use neo4rs::{Node, Query, Row}; /// Single ID field and optional timestamps. Has example of `new()` and `into_builder()` methods. @@ -46,8 +44,7 @@ impl Entity for Person { fn typename() -> &'static str { "Person" } -} -impl QueryFields for Person { + fn field_names() -> &'static [&'static str] { &["id", "name", "age", "created_at", "updated_at"] } @@ -158,8 +155,7 @@ impl Entity for PersonId { fn typename() -> &'static str { Person::typename() } -} -impl QueryFields for PersonId { + fn field_names() -> &'static [&'static str] { &["id"] } diff --git a/lib/tests/common/fixtures/worked_at.rs b/lib/tests/common/fixtures/worked_at.rs index f845808..fd2bd75 100644 --- a/lib/tests/common/fixtures/worked_at.rs +++ b/lib/tests/common/fixtures/worked_at.rs @@ -1,7 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{ - format_param, Entity, Error, Neo4jMap, QueryFields, RelationEntity, RelationId, StampMode, -}; +use cypher_dto::{format_param, Entity, Error, Neo4jMap, RelationEntity, RelationId, StampMode}; use neo4rs::{Query, Relation, Row, UnboundedRelation}; /// A relation with an ID field. @@ -15,8 +13,7 @@ impl Entity for WorkedAt { fn typename() -> &'static str { "WORKED_AT" } -} -impl QueryFields for WorkedAt { + fn field_names() -> &'static [&'static str] { &["until"] } @@ -92,8 +89,7 @@ impl Entity for WorkedAtId { fn typename() -> &'static str { WorkedAt::typename() } -} -impl QueryFields for WorkedAtId { + fn field_names() -> &'static [&'static str] { &["until"] } diff --git a/lib/tests/common/fixtures/works_at.rs b/lib/tests/common/fixtures/works_at.rs index 47fd6fc..b3e8aae 100644 --- a/lib/tests/common/fixtures/works_at.rs +++ b/lib/tests/common/fixtures/works_at.rs @@ -1,4 +1,4 @@ -use cypher_dto::{Entity, Error, QueryFields, RelationEntity, RelationId, StampMode}; +use cypher_dto::{Entity, Error, RelationEntity, RelationId, StampMode}; use neo4rs::{Query, Relation, Row, UnboundedRelation}; /// A fieldless relation. @@ -8,8 +8,7 @@ impl Entity for WorksAt { fn typename() -> &'static str { "WORKS_AT" } -} -impl QueryFields for WorksAt { + fn field_names() -> &'static [&'static str] { &[] } @@ -71,8 +70,7 @@ impl Entity for WorksAtId { fn typename() -> &'static str { WorksAt::typename() } -} -impl QueryFields for WorksAtId { + fn field_names() -> &'static [&'static str] { &[] } From f221bd0733383765715523367df56ed10d4b6bf6 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 22:01:41 -0400 Subject: [PATCH 05/24] remove node/relation shorthand attributes --- macros/src/node_relation.rs | 79 ------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 macros/src/node_relation.rs diff --git a/macros/src/node_relation.rs b/macros/src/node_relation.rs deleted file mode 100644 index 9a241e1..0000000 --- a/macros/src/node_relation.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{ - derive::{derive_serde, EntityType}, - stamps::Stamps, -}; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, LitStr}; - -pub fn cypher_entity_impl(attr: TokenStream, input: TokenStream, typ: EntityType) -> TokenStream { - let orig_attr = match typ { - EntityType::Node => "node", - EntityType::Relation => "relation", - }; - let input = parse_macro_input!(input as DeriveInput); - for attr in input.attrs.iter() { - if attr.path().is_ident("stamps") { - panic!("Use {}(stamps = \"...\") instead.", orig_attr); - } else if attr.path().is_ident("name") { - panic!("Use {}(name = \"...\") instead.", orig_attr); - } - } - let args = parse_macro_input!(attr as EntityArgs); - let stamps = args.stamps.map(|s| s.into_attribute()).unwrap_or(quote!()); - let name = args - .name - .map(|name| quote!(#[name = #name])) - .unwrap_or(quote!()); - - let serde = derive_serde(); - let derive = match typ { - EntityType::Node => quote!(::cypher_dto::Node), - EntityType::Relation => quote!(::cypher_dto::Relation), - }; - quote! { - #stamps - #[derive(Clone, Debug, PartialEq, #derive, #serde)] - #name - #input - } - .into() -} -struct EntityArgs { - stamps: Option, - name: Option, -} -impl syn::parse::Parse for EntityArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut stamps: Option = None; - let mut name: Option = None; - - while !input.is_empty() { - let lookahead = input.lookahead1(); - if lookahead.peek(syn::Ident) { - let ident: syn::Ident = input.parse()?; - if ident == "stamps" { - // Check if there's an equals sign after "stamps" - if input.peek(syn::Token![=]) { - let _ = input.parse::()?; - stamps = Some(input.parse()?); - } else { - // If no value is provided, use the default value for "stamps" - stamps = Some(Stamps::Full); - } - } else if ident == "name" { - let _ = input.parse::()?; - name = Some(input.parse::()?.value()); - } else { - return Err(lookahead.error()); - } - - // If there's a comma after this argument, parse it - let _ = input.parse::().ok(); - } else { - return Err(lookahead.error()); - } - } - Ok(EntityArgs { stamps, name }) - } -} From 2a5ebc4da0f1e74be47da4d2364f85b57582fb3f Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 22:02:47 -0400 Subject: [PATCH 06/24] rename attr: stamps -> timestamps --- macros/src/lib.rs | 27 +++++-------------------- macros/src/{stamps.rs => timestamps.rs} | 24 ++++------------------ 2 files changed, 9 insertions(+), 42 deletions(-) rename macros/src/{stamps.rs => timestamps.rs} (80%) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 55d62b7..ff92161 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,22 +1,21 @@ use proc_macro::TokenStream; mod derive; -mod node_relation; -mod stamps; +mod timestamps; use derive::{Node, Relation}; use syn::{parse_macro_input, DeriveInput}; /// Derives the [NodeEntity](cypher_dto::NodeEntity) and related traits. #[proc_macro_derive(Node, attributes(name, id))] -pub fn derive_cypher_node(input: TokenStream) -> TokenStream { +pub fn derive_node(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); Node::new(input).to_token_stream() } /// Derives the [RelationEntity](cypher_dto::RelationEntity) and related traits. #[proc_macro_derive(Relation, attributes(name, id))] -pub fn derive_cypher_relation(input: TokenStream) -> TokenStream { +pub fn derive_relation(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); Relation::new(input).to_token_stream() } @@ -28,22 +27,6 @@ pub fn derive_cypher_relation(input: TokenStream) -> TokenStream { /// On structs marked with `#[derive(Node)]` or `#[derive(Relation)]`, /// their `::new()` implementation already checks for Optional timestamp fields will set them to `None`. #[proc_macro_attribute] -pub fn stamps(args: TokenStream, input: TokenStream) -> TokenStream { - stamps::stamps_impl(args, input) -} - -/// Shorthand for `#[derive(Node)]`, `#[stamps]`, and other common derive implementations. -/// -/// The default derives are `Clone`, `Debug`, `PartialEq`, and if the `serde` feature is enabled: `Serialize`, and `Deserialize`. -#[proc_macro_attribute] -pub fn node(args: TokenStream, input: TokenStream) -> TokenStream { - node_relation::cypher_entity_impl(args, input, derive::EntityType::Node) -} - -/// Shorthand for `#[derive(Relation)]`, `#[stamps]`, and other common derive implementations. -/// -/// The default derives are `Clone`, `Debug`, `PartialEq`, and if the `serde` feature is enabled: `Serialize`, and `Deserialize`. -#[proc_macro_attribute] -pub fn relation(args: TokenStream, input: TokenStream) -> TokenStream { - node_relation::cypher_entity_impl(args, input, derive::EntityType::Relation) +pub fn timestamps(args: TokenStream, input: TokenStream) -> TokenStream { + timestamps::stamps_impl(args, input) } diff --git a/macros/src/stamps.rs b/macros/src/timestamps.rs similarity index 80% rename from macros/src/stamps.rs rename to macros/src/timestamps.rs index fe94b2d..ba1a8bc 100644 --- a/macros/src/stamps.rs +++ b/macros/src/timestamps.rs @@ -1,7 +1,7 @@ use std::error::Error; use proc_macro::TokenStream; -use quote::{__private::TokenStream as TokenStream2, format_ident, quote}; +use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, parse_quote, DeriveInput, Fields, Ident, LitStr, Result as SynResult, Type, @@ -14,7 +14,7 @@ pub fn stamps_impl(args: TokenStream, input: TokenStream) -> TokenStream { let name = &input.ident; let data = match &input.data { syn::Data::Struct(s) => &s.fields, - _ => panic!("#[stamps] can only be used with structs"), + _ => panic!("#[timestamps] can only be used with structs"), }; let input_attrs = input.attrs; let input_vis = input.vis; @@ -30,7 +30,7 @@ pub fn stamps_impl(args: TokenStream, input: TokenStream) -> TokenStream { #vis #name: #ty, } }), - _ => panic!("stamps can only be used on structs with named fields"), + _ => panic!("#[timestamps] can only be used on structs with named fields"), }; let (stamp_idents, stamp_types) = stamps.into_fields(); @@ -74,7 +74,7 @@ impl TryFrom<&str> for Stamps { "updated_at" => Ok(Stamps::UpdatedAt), "full" => Ok(Stamps::Full), "short" => Ok(Stamps::Short), - _ => Err(format!("Invalid `#[stamps]` argument: \"{}\". Allowed: full, short, created, updated, created_at, updated_at.", s).into()), + _ => Err(format!("Invalid `#[timestamps]` argument: \"{}\". Allowed: full, short, created, updated, created_at, updated_at.", s).into()), } } } @@ -112,20 +112,4 @@ impl Stamps { (idents, types) } - - pub fn as_str(&self) -> &'static str { - match self { - Stamps::Created => "created", - Stamps::Updated => "updated", - Stamps::CreatedAt => "created_at", - Stamps::UpdatedAt => "updated_at", - Stamps::Full => "full", - Stamps::Short => "short", - } - } - - pub fn into_attribute(self) -> TokenStream2 { - let s = self.as_str(); - quote!(#[stamps(#s)]) - } } From 2968e4138bc52d714164ae0621ccd35cd34bdd3c Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Wed, 16 Aug 2023 22:03:10 -0400 Subject: [PATCH 07/24] update examples, test, clippy --- example/src/person.rs | 14 ++++++++------ example/src/worked_at.rs | 2 +- example/tests/basic_crud.rs | 2 +- macros/src/derive.rs | 3 --- macros/src/derive/entity/field/field_type.rs | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/example/src/person.rs b/example/src/person.rs index 20a0536..04ef052 100644 --- a/example/src/person.rs +++ b/example/src/person.rs @@ -1,7 +1,9 @@ -use cypher_dto::{node, relation, stamps}; +use cypher_dto::{timestamps, Node, Relation}; /// Single ID field and optional timestamps. Has example of `new()` and `into_builder()` methods. -#[node(stamps, name = "Person2")] +#[timestamps] +#[derive(Node, Clone)] +#[name = "Person2"] pub struct Person { id: String, #[name = "name2"] @@ -11,12 +13,12 @@ pub struct Person { colors: Vec, } -#[relation] +#[derive(Relation)] struct Knows; #[cfg(test)] mod tests { - use cypher_dto::{NodeEntity, RelationBound}; + use cypher_dto::{Entity, NodeEntity, RelationBound, RelationEntity}; use super::*; @@ -34,17 +36,17 @@ mod tests { &["red".to_owned(), "blue".to_owned()], ); assert_eq!(p.id(), "id"); - let p = p.into_builder().name("name2").build().unwrap(); + let p = p.into_builder().name("name2").build(); assert_eq!(p.name(), "name2"); assert_eq!(p.colors(), &["red", "blue"]); assert_eq!(p.age(), Some(42)); let now = chrono::Utc::now(); + assert_eq!( p.clone() .into_builder() .created_at(Some(now)) .build() - .unwrap() .created_at(), Some(&now), ); diff --git a/example/src/worked_at.rs b/example/src/worked_at.rs index ad542d2..1b81eb6 100644 --- a/example/src/worked_at.rs +++ b/example/src/worked_at.rs @@ -14,7 +14,7 @@ pub struct WorkedAt { #[cfg(test)] mod tests { use super::*; - use cypher_dto::{QueryFields, StampMode}; + use cypher_dto::{Entity, StampMode}; #[test] fn rename() { diff --git a/example/tests/basic_crud.rs b/example/tests/basic_crud.rs index 20da4c5..8c41344 100644 --- a/example/tests/basic_crud.rs +++ b/example/tests/basic_crud.rs @@ -46,7 +46,7 @@ async fn basic_crud() { assert_eq!(alice.identifier(), alice_id); // Update Alice's name - let alice = alice.into_builder().name("Allison").build().unwrap(); + let alice = alice.into_builder().name("Allison").build(); graph.run(alice.update()).await.unwrap(); let mut stream = graph.execute(alice_id.read()).await.unwrap(); diff --git a/macros/src/derive.rs b/macros/src/derive.rs index 578e5aa..7376609 100644 --- a/macros/src/derive.rs +++ b/macros/src/derive.rs @@ -69,8 +69,5 @@ mod tests { assert_eq!(parse_name_meta(&attr.meta), Some("Foo".to_owned())); let attr: Attribute = parse_quote!(#[name = "Foo"]); assert_eq!(parse_name_meta(&attr.meta), Some("Foo".to_owned())); - let args = quote!(#[foo(name = "Foo", stamps = "full")]); - let meta: Meta = syn::parse2(args).unwrap(); - assert_eq!(parse_name_meta(&meta), Some("Foo".to_owned())); } } diff --git a/macros/src/derive/entity/field/field_type.rs b/macros/src/derive/entity/field/field_type.rs index 022940d..0691032 100644 --- a/macros/src/derive/entity/field/field_type.rs +++ b/macros/src/derive/entity/field/field_type.rs @@ -119,7 +119,7 @@ impl ArgHelper { field_into_getter_suffix, } } else { - let is_copy = is_copy_type(&ty); + let is_copy = is_copy_type(ty); Self { arg_type: ty.clone(), arg_into_field_suffix: quote!(), From 01aa390c2e7ae85611341e0d371898c8af8035b2 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Sun, 20 Aug 2023 06:17:40 -0400 Subject: [PATCH 08/24] build and test the workspace --- .github/workflows/rust.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3fde6d6..464d816 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,25 +2,20 @@ name: Rust on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: | - cd lib - cargo build --verbose - - name: Run tests - run: | - cd example - cargo test --verbose + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From d053fd9ab17cf48e0bdddc9b6a539642d9575f40 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Sun, 20 Aug 2023 11:13:43 -0400 Subject: [PATCH 09/24] rename ci action so its more descriptive in the badge --- .github/workflows/{rust.yml => ci.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{rust.yml => ci.yml} (100%) diff --git a/.github/workflows/rust.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/rust.yml rename to .github/workflows/ci.yml From f506535a4b36d2f431786214e829c0c2876223c2 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Sun, 20 Aug 2023 11:15:21 -0400 Subject: [PATCH 10/24] rename Entity to FieldSet, so it's more clear it is more abstract --- example/src/person.rs | 2 +- example/src/worked_at.rs | 2 +- lib/src/entity.rs | 16 +++++++++------- lib/src/lib.rs | 3 ++- lib/src/node.rs | 20 ++++++++++++++++---- lib/src/relationship.rs | 6 +++--- lib/tests/common/fixtures/company.rs | 6 +++--- lib/tests/common/fixtures/person.rs | 6 +++--- lib/tests/common/fixtures/worked_at.rs | 6 +++--- lib/tests/common/fixtures/works_at.rs | 6 +++--- macros/src/derive/entity.rs | 2 +- 11 files changed, 45 insertions(+), 30 deletions(-) diff --git a/example/src/person.rs b/example/src/person.rs index 04ef052..8ab92db 100644 --- a/example/src/person.rs +++ b/example/src/person.rs @@ -18,7 +18,7 @@ struct Knows; #[cfg(test)] mod tests { - use cypher_dto::{Entity, NodeEntity, RelationBound, RelationEntity}; + use cypher_dto::{FieldSet, NodeEntity, RelationBound, RelationEntity}; use super::*; diff --git a/example/src/worked_at.rs b/example/src/worked_at.rs index 1b81eb6..dde8552 100644 --- a/example/src/worked_at.rs +++ b/example/src/worked_at.rs @@ -14,7 +14,7 @@ pub struct WorkedAt { #[cfg(test)] mod tests { use super::*; - use cypher_dto::{Entity, StampMode}; + use cypher_dto::{FieldSet, StampMode}; #[test] fn rename() { diff --git a/lib/src/entity.rs b/lib/src/entity.rs index c6e2b0f..7ea0ceb 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -1,8 +1,10 @@ use crate::{format_query_fields, Stamps}; use neo4rs::{Query, Row}; -/// A named collection of [QueryFields], such as a node or relationship. -pub trait Entity: TryFrom { +/// The full or partial fields on a node or relationship that may have timestamps. +/// +/// This is the basic unit of query building used by [NodeEntity], [NodeId], [RelationEntity], and [RelationId]. +pub trait FieldSet: TryFrom { /// The primary label for a node, or the type of a relationship. fn typename() -> &'static str; @@ -32,7 +34,7 @@ pub trait Entity: TryFrom { [other_fields, stamps].join(", ") } - /// Adds all field values to the query parameters, matching placeholders in [as_query_fields]. + /// Adds all field values to the query parameters, matching placeholders in [as_query_fields()]. fn add_values_to_params(&self, query: Query, prefix: Option<&str>, mode: StampMode) -> Query; /// Formatted like `typename() { as_query_fields() }`, or for a fieldless relationship, just `typename()`. @@ -106,7 +108,7 @@ pub(crate) mod tests { // // Foo impl // - impl Entity for Foo { + impl FieldSet for Foo { fn typename() -> &'static str { "Foo" } @@ -141,7 +143,7 @@ pub(crate) mod tests { // // Bar impl // - impl Entity for Bar { + impl FieldSet for Bar { fn typename() -> &'static str { "Bar" } @@ -188,7 +190,7 @@ pub(crate) mod tests { // // Baz impl // - impl Entity for Baz { + impl FieldSet for Baz { fn typename() -> &'static str { "BAZ" } @@ -416,7 +418,7 @@ pub(crate) mod tests { } } } - impl Entity for NumTypes { + impl FieldSet for NumTypes { fn typename() -> &'static str { "NumTypes" } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 068aeb6..867904a 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -19,6 +19,7 @@ //! //! Dynamically added methods: //! 1. `fn into_values(self)` - returns a tuple of all the values in the struct. +#![warn(missing_docs)] mod entity; mod error; @@ -30,7 +31,7 @@ mod stamps; #[cfg(feature = "macros")] pub use cypher_dto_macros::*; -pub use entity::{Entity, StampMode}; +pub use entity::{FieldSet, StampMode}; pub use error::Error; pub use format::{format_param, format_query_fields}; pub use node::{NodeEntity, NodeId}; diff --git a/lib/src/node.rs b/lib/src/node.rs index 5da1966..7228abb 100644 --- a/lib/src/node.rs +++ b/lib/src/node.rs @@ -1,11 +1,20 @@ -use crate::{Entity, StampMode}; +use crate::{FieldSet, StampMode}; use neo4rs::{Node, Query}; /// A node [Entity]. -pub trait NodeEntity: Entity + TryFrom { +pub trait NodeEntity: FieldSet + TryFrom { type Id: NodeId; + /// Get the [NodeId] for this entity. + /// + /// This is less efficient than using self.into(), but is useful when you + /// don't want to consume the entity. + /// + /// The implementation in derive will clone the individual ID fields as + /// necessary. fn identifier(&self) -> Self::Id; + + /// Convenience method for `self.into()`. fn into_identifier(self) -> Self::Id { self.into() } @@ -32,9 +41,10 @@ pub trait NodeEntity: Entity + TryFrom { } /// The identifying fields of a [NodeEntity]. -pub trait NodeId: Entity + From + TryFrom { - type T: NodeEntity; +pub trait NodeId: FieldSet + From + TryFrom { + type T: NodeEntity; + /// Read a [NodeEntity] by its id, using "n" as the variable for the node. fn read(&self) -> Query { let q = Query::new(format!( "MATCH (n:{}) RETURN n", @@ -42,6 +52,8 @@ pub trait NodeId: Entity + From + TryFrom { )); self.add_values_to_params(q, None, StampMode::Read) } + + /// Delete a [NodeEntity] by its id, using "n" as the variable for the node. fn delete(&self) -> Query { let q = Query::new(format!( "MATCH (n:{}) DETACH DELETE n", diff --git a/lib/src/relationship.rs b/lib/src/relationship.rs index 9669aab..8cff6d5 100644 --- a/lib/src/relationship.rs +++ b/lib/src/relationship.rs @@ -1,8 +1,8 @@ -use crate::{Entity, NodeEntity, NodeId, StampMode}; +use crate::{FieldSet, NodeEntity, NodeId, StampMode}; use neo4rs::{Query, Relation, UnboundedRelation}; /// A relationship [Entity]. -pub trait RelationEntity: Entity + TryFrom + TryFrom { +pub trait RelationEntity: FieldSet + TryFrom + TryFrom { type Id: RelationId; fn identifier(&self) -> Self::Id; @@ -85,7 +85,7 @@ pub trait RelationEntity: Entity + TryFrom + TryFrom + TryFrom + TryFrom + FieldSet + From + TryFrom + TryFrom { type T: RelationEntity; diff --git a/lib/tests/common/fixtures/company.rs b/lib/tests/common/fixtures/company.rs index 7365ed9..722b28f 100644 --- a/lib/tests/common/fixtures/company.rs +++ b/lib/tests/common/fixtures/company.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, StampMode}; +use cypher_dto::{format_param, Error, FieldSet, Neo4jMap, NodeEntity, NodeId, StampMode}; use neo4rs::{Node, Query, Row}; /// Has a multi-valued ID and required timestamps. @@ -10,7 +10,7 @@ pub struct Company { pub created: DateTime, pub updated: DateTime, } -impl Entity for Company { +impl FieldSet for Company { fn typename() -> &'static str { "Company" } @@ -114,7 +114,7 @@ impl TryFrom for CompanyId { }) } } -impl Entity for CompanyId { +impl FieldSet for CompanyId { fn typename() -> &'static str { Company::typename() } diff --git a/lib/tests/common/fixtures/person.rs b/lib/tests/common/fixtures/person.rs index 612154a..356e2c9 100644 --- a/lib/tests/common/fixtures/person.rs +++ b/lib/tests/common/fixtures/person.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{format_param, Entity, Error, Neo4jMap, NodeEntity, NodeId, StampMode}; +use cypher_dto::{format_param, Error, FieldSet, Neo4jMap, NodeEntity, NodeId, StampMode}; use neo4rs::{Node, Query, Row}; /// Single ID field and optional timestamps. Has example of `new()` and `into_builder()` methods. @@ -40,7 +40,7 @@ impl Person { self.into() } } -impl Entity for Person { +impl FieldSet for Person { fn typename() -> &'static str { "Person" } @@ -151,7 +151,7 @@ impl TryFrom for PersonId { }) } } -impl Entity for PersonId { +impl FieldSet for PersonId { fn typename() -> &'static str { Person::typename() } diff --git a/lib/tests/common/fixtures/worked_at.rs b/lib/tests/common/fixtures/worked_at.rs index fd2bd75..89c0910 100644 --- a/lib/tests/common/fixtures/worked_at.rs +++ b/lib/tests/common/fixtures/worked_at.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, Utc}; -use cypher_dto::{format_param, Entity, Error, Neo4jMap, RelationEntity, RelationId, StampMode}; +use cypher_dto::{format_param, Error, FieldSet, Neo4jMap, RelationEntity, RelationId, StampMode}; use neo4rs::{Query, Relation, Row, UnboundedRelation}; /// A relation with an ID field. @@ -9,7 +9,7 @@ use neo4rs::{Query, Relation, Row, UnboundedRelation}; pub struct WorkedAt { pub until: DateTime, } -impl Entity for WorkedAt { +impl FieldSet for WorkedAt { fn typename() -> &'static str { "WORKED_AT" } @@ -85,7 +85,7 @@ impl TryFrom for WorkedAtId { }) } } -impl Entity for WorkedAtId { +impl FieldSet for WorkedAtId { fn typename() -> &'static str { WorkedAt::typename() } diff --git a/lib/tests/common/fixtures/works_at.rs b/lib/tests/common/fixtures/works_at.rs index b3e8aae..68e491d 100644 --- a/lib/tests/common/fixtures/works_at.rs +++ b/lib/tests/common/fixtures/works_at.rs @@ -1,10 +1,10 @@ -use cypher_dto::{Entity, Error, RelationEntity, RelationId, StampMode}; +use cypher_dto::{Error, FieldSet, RelationEntity, RelationId, StampMode}; use neo4rs::{Query, Relation, Row, UnboundedRelation}; /// A fieldless relation. #[derive(Clone, Debug, PartialEq)] pub struct WorksAt {} -impl Entity for WorksAt { +impl FieldSet for WorksAt { fn typename() -> &'static str { "WORKS_AT" } @@ -66,7 +66,7 @@ impl TryFrom for WorksAtId { Ok(Self {}) } } -impl Entity for WorksAtId { +impl FieldSet for WorksAtId { fn typename() -> &'static str { WorksAt::typename() } diff --git a/macros/src/derive/entity.rs b/macros/src/derive/entity.rs index b40a49e..5edd3bf 100644 --- a/macros/src/derive/entity.rs +++ b/macros/src/derive/entity.rs @@ -66,7 +66,7 @@ impl Entity { let new_and_getters = new_and_getters::impl_new_and_getters(self); quote! { - impl ::cypher_dto::Entity for #struct_ident { + impl ::cypher_dto::FieldSet for #struct_ident { fn typename() -> &'static str { #struct_name } From 57fc7a9348039dfed6d0309a97641e44a4a5b7c2 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Sun, 20 Aug 2023 11:16:12 -0400 Subject: [PATCH 11/24] wip changing readme --- lib/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/README.md b/lib/README.md index 45ddb65..db194bd 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,20 +1,27 @@ # cypher-dto [![Crates.io](https://img.shields.io/crates/v/cypher-dto)](https://crates.io/crates/cypher-dto) -[![Github.com](https://github.com/jifalops/cypher-dto/actions/workflows/rust.yml/badge.svg)](https://github.com/jifalops/cypher-dto/actions/workflows/rust.yml) +[![Github.com](https://github.com/jifalops/cypher-dto/actions/workflows/ci.yml/badge.svg)](https://github.com/jifalops/cypher-dto/actions/workflows/ci.yml) [![Docs.rs](https://docs.rs/cypher-dto/badge.svg)](https://docs.rs/cypher-dto) ![License](https://img.shields.io/crates/l/cypher-dto.svg) -A collection of traits and macros for working Data Transfer Objects (DTOs) in Neo4j. +A collection of traits and macros for working with Data Transfer Objects (DTOs) in Neo4j. +```rust +use cypher_dto::Node; +#[derive(Node)] +struct Person { + name: String +} +``` ## Examples ### Basic usage ```rust -#[node] +#[derive(Node)] struct Person { id: String, // Inferred to be the only #[id] field. name: String, From ca1274e9d8401a060de5ab5897c6ab1854a12b6b Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Mon, 21 Aug 2023 11:00:53 -0400 Subject: [PATCH 12/24] add/move scripts --- coverage.sh | 5 +++++ doc.sh | 8 ++++++++ lib/coverage.sh | 5 ----- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100755 coverage.sh create mode 100755 doc.sh delete mode 100755 lib/coverage.sh diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 0000000..97b5e94 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +PORT=${1:-8081} +cargo llvm-cov test --html +python3 -m http.server $PORT --directory target/llvm-cov/html diff --git a/doc.sh b/doc.sh new file mode 100755 index 0000000..4f51a5a --- /dev/null +++ b/doc.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e +PORT=${1:-8080} +cargo doc --no-deps +echo "=================================" +echo "http://localhost:$PORT/cypher_dto" +echo "=================================" +python3 -m http.server $PORT --directory target/doc/ diff --git a/lib/coverage.sh b/lib/coverage.sh deleted file mode 100755 index e276b98..0000000 --- a/lib/coverage.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -cargo llvm-cov nextest --html -pushd ../target/llvm-cov/html > /dev/null -python3 -m http.server 14532 -popd > /dev/null From f8228cf182bef2979ada68a54d439b561b61088b Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Mon, 21 Aug 2023 11:02:17 -0400 Subject: [PATCH 13/24] rename as_query* to to_query* because it's not free --- example/src/worked_at.rs | 2 +- example/tests/manual_queries.rs | 10 ++--- lib/src/entity.rs | 54 ++++++++------------------ lib/src/lib.rs | 7 ++-- lib/src/node.rs | 10 ++--- lib/src/relationship.rs | 67 +++++++++++++++++++-------------- lib/src/stamps.rs | 2 - lib/tests/manual_queries.rs | 12 +++--- 8 files changed, 75 insertions(+), 89 deletions(-) diff --git a/example/src/worked_at.rs b/example/src/worked_at.rs index dde8552..a8fb8be 100644 --- a/example/src/worked_at.rs +++ b/example/src/worked_at.rs @@ -19,7 +19,7 @@ mod tests { #[test] fn rename() { assert_eq!( - WorkedAt::as_query_fields(None, StampMode::Read), + WorkedAt::to_query_fields(None, StampMode::Read), "foo: $foo" ); } diff --git a/example/tests/manual_queries.rs b/example/tests/manual_queries.rs index a219e36..eb68682 100644 --- a/example/tests/manual_queries.rs +++ b/example/tests/manual_queries.rs @@ -39,11 +39,11 @@ async fn create_all_at_once() { CREATE (bob)-[w2:{}]->(acme) RETURN alice, bob, acme, w, w2 ", - Person::as_query_obj(Some("alice"), StampMode::Create), - Person::as_query_obj(Some("bob"), StampMode::Create), - Company::as_query_obj(Some("acme"), StampMode::Create), - WorkedAt::as_query_obj(Some("w"), StampMode::Create), - WorkedAt::as_query_obj(Some("w2"), StampMode::Create), + Person::to_query_obj(Some("alice"), StampMode::Create), + Person::to_query_obj(Some("bob"), StampMode::Create), + Company::to_query_obj(Some("acme"), StampMode::Create), + WorkedAt::to_query_obj(Some("w"), StampMode::Create), + WorkedAt::to_query_obj(Some("w2"), StampMode::Create), )); q = alice.add_values_to_params(q, Some("alice"), StampMode::Create); q = bob.add_values_to_params(q, Some("bob"), StampMode::Create); diff --git a/lib/src/entity.rs b/lib/src/entity.rs index 7ea0ceb..67a8f15 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -21,7 +21,7 @@ pub trait FieldSet: TryFrom { /// `struct Foo { bar: u8 }` would be `bar: $bar`. /// /// Prefixes apply to the placeholders only (e.g. bar: $prefix_bar). - fn as_query_fields(prefix: Option<&str>, mode: StampMode) -> String { + fn to_query_fields(prefix: Option<&str>, mode: StampMode) -> String { let (stamps, other_fields) = Self::timestamps(); let stamps = stamps.as_query_fields(prefix, mode); let other_fields = format_query_fields(other_fields, prefix); @@ -38,8 +38,8 @@ pub trait FieldSet: TryFrom { fn add_values_to_params(&self, query: Query, prefix: Option<&str>, mode: StampMode) -> Query; /// Formatted like `typename() { as_query_fields() }`, or for a fieldless relationship, just `typename()`. - fn as_query_obj(prefix: Option<&str>, mode: StampMode) -> String { - let fields = Self::as_query_fields(prefix, mode); + fn to_query_obj(prefix: Option<&str>, mode: StampMode) -> String { + let fields = Self::to_query_fields(prefix, mode); if fields.is_empty() { return Self::typename().to_owned(); } @@ -64,28 +64,6 @@ pub enum StampMode { Update, } -// /// An observable for working with [QueryFields] and [neo4rs::Query]s. -// pub struct QueryBuilder { -// parts: Vec, -// params: HashSet, -// } -// impl QueryBuilder { -// pub fn new() -> Self { -// Self { -// parts: Vec::new(), -// params: HashSet::new(), -// } -// } -// pub fn add(&mut self, s: &str, params: &[&str]) -> &Self { -// self.parts.push(s.to_owned()); -// self.params.extend(params.iter().map(|p| (*p).to_owned())); -// self -// } -// pub fn build(self) -> (String, HashSet) { -// (self.parts.join(" "), self.params) -// } -// } - #[cfg(test)] pub(crate) mod tests { use super::*; @@ -214,24 +192,24 @@ pub(crate) mod tests { fn as_obj() { // Foo assert_eq!( - Foo::as_query_obj(None, StampMode::Read), + Foo::to_query_obj(None, StampMode::Read), "Foo { name: $name, age: $age }" ); // Bar assert_eq!( - Bar::as_query_obj(None, StampMode::Read), + Bar::to_query_obj(None, StampMode::Read), "Bar { created: $created, updated: $updated }" ); assert_eq!( - Bar::as_query_obj(None, StampMode::Create), + Bar::to_query_obj(None, StampMode::Create), "Bar { created: datetime(), updated: datetime() }" ); assert_eq!( - Bar::as_query_obj(None, StampMode::Update), + Bar::to_query_obj(None, StampMode::Update), "Bar { created: $created, updated: datetime() }" ); // Baz - assert_eq!(Baz::as_query_obj(None, StampMode::Read), "BAZ"); + assert_eq!(Baz::to_query_obj(None, StampMode::Read), "BAZ"); } #[test] @@ -249,7 +227,7 @@ pub(crate) mod tests { // Foo let mut q = Query::new(format!( "CREATE (n:{})", - Foo::as_query_obj(None, StampMode::Create) + Foo::to_query_obj(None, StampMode::Create) )); q = foo.add_values_to_params(q, None, StampMode::Create); assert!(q.has_param_key("name")); @@ -258,7 +236,7 @@ pub(crate) mod tests { // Bar let mut q = Query::new(format!( "MATCH (n:{})", - Bar::as_query_obj(None, StampMode::Read) + Bar::to_query_obj(None, StampMode::Read) )); q = bar.add_values_to_params(q, None, StampMode::Read); assert!(q.has_param_key("created")); @@ -266,7 +244,7 @@ pub(crate) mod tests { let mut q = Query::new(format!( "CREATE (n:{})", - Bar::as_query_obj(None, StampMode::Create) + Bar::to_query_obj(None, StampMode::Create) )); q = bar.add_values_to_params(q, None, StampMode::Create); assert!(!q.has_param_key("created")); @@ -274,7 +252,7 @@ pub(crate) mod tests { let mut q = Query::new(format!( "MERGE (n:{})", - Bar::as_query_obj(None, StampMode::Update) + Bar::to_query_obj(None, StampMode::Update) )); q = bar.add_values_to_params(q, None, StampMode::Update); assert!(q.has_param_key("created")); @@ -285,9 +263,9 @@ pub(crate) mod tests { "MATCH (s:{}) MATCH (e:{}) CREATE (s)-[r:{}]->(e)", - Foo::as_query_obj(Some("s"), StampMode::Read), - Bar::as_query_obj(Some("e"), StampMode::Read), - Baz::as_query_obj(None, StampMode::Create), + Foo::to_query_obj(Some("s"), StampMode::Read), + Bar::to_query_obj(Some("e"), StampMode::Read), + Baz::to_query_obj(None, StampMode::Create), )); q = foo.add_values_to_params(q, Some("s"), StampMode::Read); q = bar.add_values_to_params(q, Some("e"), StampMode::Read); @@ -332,7 +310,7 @@ pub(crate) mod tests { }; let mut q = Query::new(format!( "CREATE (n:{})", - NumTypes::as_query_obj(None, StampMode::Create) + NumTypes::to_query_obj(None, StampMode::Create) )); q = num_types.add_values_to_params(q, None, StampMode::Create); assert!(q.has_param_key("usize_num")); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 867904a..2950834 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,5 +1,4 @@ -//! A low-level abstraction for working with serialization, storage, and IPC. -//! [Data Transfer Object](https://en.wikipedia.org/wiki/Data_transfer_object) +//! A collection of traits and macros for working Data Transfer Objects (DTOs) Cypher and Neo4j. //! //! It works with key-value pairs; only structs with named fields are supported. //! @@ -19,7 +18,9 @@ //! //! Dynamically added methods: //! 1. `fn into_values(self)` - returns a tuple of all the values in the struct. + #![warn(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] mod entity; mod error; @@ -29,7 +30,7 @@ mod relationship; mod stamps; #[cfg(feature = "macros")] -pub use cypher_dto_macros::*; +pub use cypher_dto_macros::{timestamps, Node, Relation}; pub use entity::{FieldSet, StampMode}; pub use error::Error; diff --git a/lib/src/node.rs b/lib/src/node.rs index 7228abb..9e86af6 100644 --- a/lib/src/node.rs +++ b/lib/src/node.rs @@ -22,7 +22,7 @@ pub trait NodeEntity: FieldSet + TryFrom { fn create(&self) -> Query { let q = Query::new(format!( "CREATE (n:{})", - Self::as_query_obj(None, StampMode::Create), + Self::to_query_obj(None, StampMode::Create), )); self.add_values_to_params(q, None, StampMode::Create) } @@ -33,8 +33,8 @@ pub trait NodeEntity: FieldSet + TryFrom { fn update(&self) -> Query { let q = Query::new(format!( "MATCH (n:{}) SET n += {{ {} }}", - Self::Id::as_query_obj(None, StampMode::Read), - Self::as_query_fields(None, StampMode::Update), + Self::Id::to_query_obj(None, StampMode::Read), + Self::to_query_fields(None, StampMode::Update), )); self.add_values_to_params(q, None, StampMode::Update) } @@ -48,7 +48,7 @@ pub trait NodeId: FieldSet + From + TryFrom { fn read(&self) -> Query { let q = Query::new(format!( "MATCH (n:{}) RETURN n", - Self::as_query_obj(None, StampMode::Read) + Self::to_query_obj(None, StampMode::Read) )); self.add_values_to_params(q, None, StampMode::Read) } @@ -57,7 +57,7 @@ pub trait NodeId: FieldSet + From + TryFrom { fn delete(&self) -> Query { let q = Query::new(format!( "MATCH (n:{}) DETACH DELETE n", - Self::as_query_obj(None, StampMode::Read) + Self::to_query_obj(None, StampMode::Read) )); self.add_values_to_params(q, None, StampMode::Read) } diff --git a/lib/src/relationship.rs b/lib/src/relationship.rs index 8cff6d5..b279990 100644 --- a/lib/src/relationship.rs +++ b/lib/src/relationship.rs @@ -1,11 +1,20 @@ use crate::{FieldSet, NodeEntity, NodeId, StampMode}; use neo4rs::{Query, Relation, UnboundedRelation}; -/// A relationship [Entity]. +/// A relationship entity. pub trait RelationEntity: FieldSet + TryFrom + TryFrom { type Id: RelationId; + /// Get the [RelationId] for this entity. + /// + /// This is less efficient than using self.into(), but is useful when you + /// don't want to consume the entity. + /// + /// The implementation in derive will clone the individual ID fields as + /// necessary. fn identifier(&self) -> Self::Id; + + /// Convenience method for `self.into()`. fn into_identifier(self) -> Self::Id { self.into() } @@ -23,7 +32,7 @@ pub trait RelationEntity: FieldSet + TryFrom + TryFrom + TryFrom + TryFrom + TryFrom + TryFrom + TryFrom + TryFrom + TryFrom { - type T: RelationEntity; + type T: RelationEntity; /// Use only for relations that have one or more ID fields, otherwise use the other `read_` methods. /// - /// This will read all relations of the same type if [field_names()] is empty. + /// This will read all relations of the same type if [FieldSet::field_names()] is empty. fn read(&self) -> Query { assert!(!Self::field_names().is_empty()); let q = Query::new(format!( "MATCH [r:{}] RETURN r", - Self::as_query_obj(None, StampMode::Read) + Self::to_query_obj(None, StampMode::Read) )); self.add_values_to_params(q, None, StampMode::Read) } @@ -105,8 +114,8 @@ pub trait RelationId: let mut q = Query::new(format!( "MATCH (n:{})-[r:{}]-() RETURN r", - T::as_query_obj(Some("n"), StampMode::Read), - Self::as_query_obj(None, StampMode::Read) + T::to_query_obj(Some("n"), StampMode::Read), + Self::to_query_obj(None, StampMode::Read) )); q = from.add_values_to_params(q, Some("n"), StampMode::Read); self.add_values_to_params(q, None, StampMode::Read) @@ -116,9 +125,9 @@ pub trait RelationId: let mut q = Query::new(format!( "MATCH (s:{})-[r:{}]-(e:{}) RETURN r", - S::as_query_obj(Some("s"), StampMode::Read), - Self::as_query_obj(None, StampMode::Read), - E::as_query_obj(Some("e"), StampMode::Read), + S::to_query_obj(Some("s"), StampMode::Read), + Self::to_query_obj(None, StampMode::Read), + E::to_query_obj(Some("e"), StampMode::Read), )); q = start.add_values_to_params(q, Some("s"), StampMode::Read); q = end.add_values_to_params(q, Some("e"), StampMode::Read); @@ -126,12 +135,12 @@ pub trait RelationId: } /// Use only for relations that have one or more ID fields, otherwise use the other `delete_` methods. /// - /// This will delete all relations of the same type if [field_names()] is empty. + /// This will delete all relations of the same type if [FieldSet::field_names()] is empty. fn delete(&self) -> Query { assert!(!Self::field_names().is_empty()); let q = Query::new(format!( "MATCH [r:{}] DELETE r", - Self::as_query_obj(None, StampMode::Read) + Self::to_query_obj(None, StampMode::Read) )); self.add_values_to_params(q, None, StampMode::Read) } @@ -140,8 +149,8 @@ pub trait RelationId: let mut q = Query::new(format!( "MATCH (n:{})-[r:{}]-() DELETE r", - T::as_query_obj(Some("n"), StampMode::Read), - Self::as_query_obj(None, StampMode::Read) + T::to_query_obj(Some("n"), StampMode::Read), + Self::to_query_obj(None, StampMode::Read) )); q = from.add_values_to_params(q, Some("n"), StampMode::Read); self.add_values_to_params(q, None, StampMode::Read) @@ -151,9 +160,9 @@ pub trait RelationId: let mut q = Query::new(format!( "MATCH (s:{})-[r:{}]-(e:{}) DELETE r", - S::as_query_obj(Some("s"), StampMode::Read), - Self::as_query_obj(None, StampMode::Read), - E::as_query_obj(Some("e"), StampMode::Read), + S::to_query_obj(Some("s"), StampMode::Read), + Self::to_query_obj(None, StampMode::Read), + E::to_query_obj(Some("e"), StampMode::Read), )); q = start.add_values_to_params(q, Some("s"), StampMode::Read); q = end.add_values_to_params(q, Some("e"), StampMode::Read); @@ -175,12 +184,12 @@ impl<'a, T: NodeEntity> RelationBound<'a, T> { RelationBound::Create(_) => format!( "CREATE ({}:{})", prefix, - T::as_query_obj(Some(prefix), StampMode::Create) + T::to_query_obj(Some(prefix), StampMode::Create) ), RelationBound::Match(_) => format!( "MATCH ({}:{})", prefix, - T::Id::as_query_obj(Some(prefix), StampMode::Read) + T::Id::to_query_obj(Some(prefix), StampMode::Read) ), // RelationBound::Merge(_) => { // format!( diff --git a/lib/src/stamps.rs b/lib/src/stamps.rs index 8043490..55ca974 100644 --- a/lib/src/stamps.rs +++ b/lib/src/stamps.rs @@ -60,8 +60,6 @@ impl Stamps { } /// Returns these timestamp fields as a query string, depending on the [StampMode]. - /// - /// Similar to [QueryFields::as_query_fields], but with a Stamps instance. pub fn as_query_fields(&self, prefix: Option<&str>, mode: StampMode) -> String { if self == &Stamps::None { return "".to_owned(); diff --git a/lib/tests/manual_queries.rs b/lib/tests/manual_queries.rs index 24b989d..e4cf29c 100644 --- a/lib/tests/manual_queries.rs +++ b/lib/tests/manual_queries.rs @@ -34,11 +34,11 @@ async fn create_all_at_once() { CREATE (bob)-[w2:{}]->(acme) RETURN alice, bob, acme, w, w2 ", - Person::as_query_obj(Some("alice"), StampMode::Create), - Person::as_query_obj(Some("bob"), StampMode::Create), - Company::as_query_obj(Some("acme"), StampMode::Create), - WorkedAt::as_query_obj(Some("w"), StampMode::Create), - WorkedAt::as_query_obj(Some("w2"), StampMode::Create), + Person::to_query_obj(Some("alice"), StampMode::Create), + Person::to_query_obj(Some("bob"), StampMode::Create), + Company::to_query_obj(Some("acme"), StampMode::Create), + WorkedAt::to_query_obj(Some("w"), StampMode::Create), + WorkedAt::to_query_obj(Some("w2"), StampMode::Create), )); q = alice.add_values_to_params(q, Some("alice"), StampMode::Create); q = bob.add_values_to_params(q, Some("bob"), StampMode::Create); @@ -55,7 +55,7 @@ async fn create_all_at_once() { assert_eq!(alice.name(), alice_db.name()); assert_eq!(alice.age(), alice_db.age()); - let bob_db: Person = row.get::("bob").unwrap().try_into().unwrap(); + let bob_db = Person::try_from(row.get::("bob").unwrap()).unwrap(); assert_eq!(bob.id(), bob_db.id()); assert_eq!(bob.name(), bob_db.name()); assert_eq!(bob.age(), bob_db.age()); From d2712da69763d8499bf8b1b8e3cb2e792e06e0a4 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 24 Aug 2023 13:44:27 -0400 Subject: [PATCH 14/24] add as_query_fields and as_query_obj, static query string that ignores timestamps --- example/src/person.rs | 14 +++++++- lib/src/entity.rs | 44 ++++++++++++++++++++++++++ lib/tests/common/fixtures/company.rs | 14 ++++++++ lib/tests/common/fixtures/person.rs | 12 +++++++ lib/tests/common/fixtures/worked_at.rs | 12 +++++++ lib/tests/common/fixtures/works_at.rs | 12 +++++++ macros/src/derive/entity.rs | 15 +++++++++ 7 files changed, 122 insertions(+), 1 deletion(-) diff --git a/example/src/person.rs b/example/src/person.rs index 8ab92db..713bc93 100644 --- a/example/src/person.rs +++ b/example/src/person.rs @@ -18,7 +18,7 @@ struct Knows; #[cfg(test)] mod tests { - use cypher_dto::{FieldSet, NodeEntity, RelationBound, RelationEntity}; + use cypher_dto::{FieldSet, NodeEntity, RelationBound, RelationEntity, StampMode}; use super::*; @@ -29,6 +29,18 @@ mod tests { Person::field_names(), vec!["id", "name2", "age", "colors", "created_at", "updated_at"] ); + assert_eq!( + Person::as_query_fields(), + "id: $id, name2: $name2, age: $age, colors: $colors, created_at: $created_at, updated_at: $updated_at" + ); + assert_eq!( + Person::as_query_obj(), + "Person2 { id: $id, name2: $name2, age: $age, colors: $colors, created_at: $created_at, updated_at: $updated_at }" + ); + assert_eq!( + Person::as_query_obj(), + Person::to_query_obj(None, StampMode::Read) + ); let p = Person::new( "id", "name", diff --git a/lib/src/entity.rs b/lib/src/entity.rs index 67a8f15..b3e827c 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -16,6 +16,18 @@ pub trait FieldSet: TryFrom { Stamps::from_fields(Self::field_names()) } + /// Formats the field names as a query string. + /// + /// `struct Foo { bar: u8 }` would be `bar: $bar`. + /// + /// This is a special case of [to_query_fields], where the prefix is `None` and the mode is [StampMode::Read], and is known at compile time. + fn as_query_fields() -> &'static str; + + /// Wraps the fields formatted by [as_query_fields] with [typename] and a pair of curly braces. + /// + /// `Foo { bar: $bar }` + fn as_query_obj() -> &'static str; + /// Formats the field names as a query string. /// /// `struct Foo { bar: u8 }` would be `bar: $bar`. @@ -95,6 +107,14 @@ pub(crate) mod tests { &["name", "age"] } + fn as_query_fields() -> &'static str { + "name: $name, age: $age" + } + + fn as_query_obj() -> &'static str { + "Foo { name: $name, age: $age }" + } + fn add_values_to_params(&self, query: Query, prefix: Option<&str>, _: StampMode) -> Query { query .param(&format_param("name", prefix), self.name.clone()) @@ -130,6 +150,14 @@ pub(crate) mod tests { &["created", "updated"] } + fn as_query_fields() -> &'static str { + "created: $created, updated: $updated" + } + + fn as_query_obj() -> &'static str { + "Bar { created: $created, updated: $updated }" + } + fn add_values_to_params( &self, query: Query, @@ -177,6 +205,14 @@ pub(crate) mod tests { &[] } + fn as_query_fields() -> &'static str { + "" + } + + fn as_query_obj() -> &'static str { + "BAZ" + } + fn add_values_to_params(&self, query: Query, _: Option<&str>, _: StampMode) -> Query { query } @@ -434,6 +470,14 @@ pub(crate) mod tests { ] } + fn as_query_fields() -> &'static str { + "usize_num: $usize_num, isize_num: $isize_num, u8_num: $u8_num, u16_num: $u16_num, u32_num: $u32_num, u64_num: $u64_num, u128_num: $u128_num, i8_num: $i8_num, i16_num: $i16_num, i32_num: $i32_num, i64_num: $i64_num, i128_num: $i128_num, f32_num: $f32_num, f64_num: $f64_num, usize_opt: $usize_opt, isize_opt: $isize_opt, u8_opt: $u8_opt, u16_opt: $u16_opt, u32_opt: $u32_opt, u64_opt: $u64_opt, u128_opt: $u128_opt, i8_opt: $i8_opt, i16_opt: $i16_opt, i32_opt: $i32_opt, i64_opt: $i64_opt, i128_opt: $i128_opt, f32_opt: $f32_opt, f64_opt: $f64_opt" + } + // Trusting copilot: ^^ and vv + fn as_query_obj() -> &'static str { + "NumTypes { usize_num: $usize_num, isize_num: $isize_num, u8_num: $u8_num, u16_num: $u16_num, u32_num: $u32_num, u64_num: $u64_num, u128_num: $u128_num, i8_num: $i8_num, i16_num: $i16_num, i32_num: $i32_num, i64_num: $i64_num, i128_num: $i128_num, f32_num: $f32_num, f64_num: $f64_num, usize_opt: $usize_opt, isize_opt: $isize_opt, u8_opt: $u8_opt, u16_opt: $u16_opt, u32_opt: $u32_opt, u64_opt: $u64_opt, u128_opt: $u128_opt, i8_opt: $i8_opt, i16_opt: $i16_opt, i32_opt: $i32_opt, i64_opt: $i64_opt, i128_opt: $i128_opt, f32_opt: $f32_opt, f64_opt: $f64_opt }" + } + fn add_values_to_params(&self, q: Query, prefix: Option<&str>, _: StampMode) -> Query { // These are the minimal conversions needed before neo4rs v0.7.0. q.param(&format_param("usize_num", prefix), self.usize_num as i64) diff --git a/lib/tests/common/fixtures/company.rs b/lib/tests/common/fixtures/company.rs index 722b28f..726df93 100644 --- a/lib/tests/common/fixtures/company.rs +++ b/lib/tests/common/fixtures/company.rs @@ -19,6 +19,14 @@ impl FieldSet for Company { &["name", "state", "created", "updated"] } + fn as_query_fields() -> &'static str { + "name: $name, state: $state, created: $created, updated: $updated" + } + + fn as_query_obj() -> &'static str { + "Company { name: $name, state: $state, created: $created, updated: $updated }" + } + fn add_values_to_params(&self, mut q: Query, prefix: Option<&str>, mode: StampMode) -> Query { q = q.param(&format_param("name", prefix), self.name.clone()); q = q.param(&format_param("state", prefix), self.state.clone()); @@ -122,6 +130,12 @@ impl FieldSet for CompanyId { fn field_names() -> &'static [&'static str] { &["name", "state"] } + fn as_query_fields() -> &'static str { + "name: $name, state: $state" + } + fn as_query_obj() -> &'static str { + "Company { name: $name, state: $state }" + } fn add_values_to_params(&self, query: Query, prefix: Option<&str>, _: StampMode) -> Query { query .param(&format_param("name", prefix), self.name.clone()) diff --git a/lib/tests/common/fixtures/person.rs b/lib/tests/common/fixtures/person.rs index 356e2c9..130348c 100644 --- a/lib/tests/common/fixtures/person.rs +++ b/lib/tests/common/fixtures/person.rs @@ -48,6 +48,12 @@ impl FieldSet for Person { fn field_names() -> &'static [&'static str] { &["id", "name", "age", "created_at", "updated_at"] } + fn as_query_fields() -> &'static str { + "id: $id, name: $name, age: $age, created_at: $created_at, updated_at: $updated_at" + } + fn as_query_obj() -> &'static str { + "Person { id: $id, name: $name, age: $age, created_at: $created_at, updated_at: $updated_at }" + } fn add_values_to_params(&self, mut q: Query, prefix: Option<&str>, mode: StampMode) -> Query { q = q.param(&format_param("id", prefix), self.id.clone()); q = q.param(&format_param("name", prefix), self.name.clone()); @@ -159,6 +165,12 @@ impl FieldSet for PersonId { fn field_names() -> &'static [&'static str] { &["id"] } + fn as_query_fields() -> &'static str { + "id: $id" + } + fn as_query_obj() -> &'static str { + "Person { id: $id }" + } fn add_values_to_params(&self, query: Query, prefix: Option<&str>, _: StampMode) -> Query { query.param(&format_param("id", prefix), self.id.clone()) } diff --git a/lib/tests/common/fixtures/worked_at.rs b/lib/tests/common/fixtures/worked_at.rs index 89c0910..1166629 100644 --- a/lib/tests/common/fixtures/worked_at.rs +++ b/lib/tests/common/fixtures/worked_at.rs @@ -17,6 +17,12 @@ impl FieldSet for WorkedAt { fn field_names() -> &'static [&'static str] { &["until"] } + fn as_query_fields() -> &'static str { + "until: $until" + } + fn as_query_obj() -> &'static str { + "WORKED_AT { until: $until }" + } fn add_values_to_params(&self, q: Query, prefix: Option<&str>, _mode: StampMode) -> Query { q.param(&format_param("until", prefix), self.until.fixed_offset()) @@ -93,6 +99,12 @@ impl FieldSet for WorkedAtId { fn field_names() -> &'static [&'static str] { &["until"] } + fn as_query_fields() -> &'static str { + "until: $until" + } + fn as_query_obj() -> &'static str { + "WORKED_AT { until: $until }" + } fn add_values_to_params(&self, q: Query, prefix: Option<&str>, _mode: StampMode) -> Query { q.param(&format_param("until", prefix), self.until.fixed_offset()) } diff --git a/lib/tests/common/fixtures/works_at.rs b/lib/tests/common/fixtures/works_at.rs index 68e491d..b162a84 100644 --- a/lib/tests/common/fixtures/works_at.rs +++ b/lib/tests/common/fixtures/works_at.rs @@ -12,6 +12,12 @@ impl FieldSet for WorksAt { fn field_names() -> &'static [&'static str] { &[] } + fn as_query_fields() -> &'static str { + "" + } + fn as_query_obj() -> &'static str { + "WORKS_AT" + } fn add_values_to_params(&self, query: Query, _: Option<&str>, _: StampMode) -> Query { query @@ -74,6 +80,12 @@ impl FieldSet for WorksAtId { fn field_names() -> &'static [&'static str] { &[] } + fn as_query_fields() -> &'static str { + "" + } + fn as_query_obj() -> &'static str { + "WORKS_AT" + } fn add_values_to_params(&self, query: Query, _prefix: Option<&str>, _mode: StampMode) -> Query { query } diff --git a/macros/src/derive/entity.rs b/macros/src/derive/entity.rs index 5edd3bf..dfc15e8 100644 --- a/macros/src/derive/entity.rs +++ b/macros/src/derive/entity.rs @@ -65,6 +65,13 @@ impl Entity { let new_and_getters = new_and_getters::impl_new_and_getters(self); + let as_fields = names + .iter() + .map(|n| format!("{n}: ${n}")) + .collect::>() + .join(", "); + let as_obj = format!("{} {{ {} }}", struct_name, as_fields); + quote! { impl ::cypher_dto::FieldSet for #struct_ident { fn typename() -> &'static str { @@ -75,6 +82,14 @@ impl Entity { &[#(#names),*] } + fn as_query_fields() -> &'static str { + #as_fields + } + + fn as_query_obj() -> &'static str { + #as_obj + } + fn add_values_to_params(&self, mut query: ::neo4rs::Query, prefix: Option<&str>, mode: ::cypher_dto::StampMode) -> ::neo4rs::Query { #(query = #into_params;)* query From 70b424a77d813157e35c8506a5de209a2e9b3d97 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 24 Aug 2023 18:03:41 -0400 Subject: [PATCH 15/24] rename fn --- lib/src/relationship.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/relationship.rs b/lib/src/relationship.rs index b279990..eb15e3f 100644 --- a/lib/src/relationship.rs +++ b/lib/src/relationship.rs @@ -30,8 +30,8 @@ pub trait RelationEntity: FieldSet + TryFrom + TryFrom(e) "###, - start.to_line("s"), - end.to_line("e"), + start.to_query_clause("s"), + end.to_query_clause("e"), Self::to_query_obj(None, StampMode::Create) ); // trace!("creating relation: {}", q); @@ -179,7 +179,8 @@ pub enum RelationBound<'a, T: NodeEntity> { // Merge(T), } impl<'a, T: NodeEntity> RelationBound<'a, T> { - pub fn to_line(&self, prefix: &str) -> String { + /// Returns a CREATE (node:...) or MATCH (node:...) clause for this variant. + pub fn to_query_clause(&self, prefix: &str) -> String { match self { RelationBound::Create(_) => format!( "CREATE ({}:{})", From 7addcef90666c6b4aac29a77269370d0a5083d62 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 24 Aug 2023 18:03:55 -0400 Subject: [PATCH 16/24] write a bunch of test fragments as includes --- example/src/person.rs | 2 +- lib/include/create_query.rs | 18 ++++++++ lib/include/exec/create_and_read_node.rs | 26 ++++++++++++ lib/include/exec/create_and_read_relation.rs | 40 ++++++++++++++++++ lib/include/id.rs | 10 +++++ lib/include/id_inferred.rs | 9 ++++ lib/include/id_multi.rs | 11 +++++ lib/include/read_query.rs | 19 +++++++++ lib/include/relationships.rs | 44 ++++++++++++++++++++ lib/include/rename.rs | 12 ++++++ lib/include/static_strings.rs | 10 +++++ lib/src/lib.rs | 3 ++ 12 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 lib/include/create_query.rs create mode 100644 lib/include/exec/create_and_read_node.rs create mode 100644 lib/include/exec/create_and_read_relation.rs create mode 100644 lib/include/id.rs create mode 100644 lib/include/id_inferred.rs create mode 100644 lib/include/id_multi.rs create mode 100644 lib/include/read_query.rs create mode 100644 lib/include/relationships.rs create mode 100644 lib/include/rename.rs create mode 100644 lib/include/static_strings.rs diff --git a/example/src/person.rs b/example/src/person.rs index 713bc93..c65e230 100644 --- a/example/src/person.rs +++ b/example/src/person.rs @@ -27,7 +27,7 @@ mod tests { assert_eq!(Person::typename(), "Person2"); assert_eq!( Person::field_names(), - vec!["id", "name2", "age", "colors", "created_at", "updated_at"] + ["id", "name2", "age", "colors", "created_at", "updated_at"] ); assert_eq!( Person::as_query_fields(), diff --git a/lib/include/create_query.rs b/lib/include/create_query.rs new file mode 100644 index 0000000..af5b999 --- /dev/null +++ b/lib/include/create_query.rs @@ -0,0 +1,18 @@ +{ + #[derive(Node)] + struct Person { + name: String, + } + + // Build a query string: + let query = format!("CREATE (n:{})", Person::as_query_obj()); + assert_eq!(query, "CREATE (n:Person { name: $name })"); + + // Use it in a [neo4rs::Query]: + let mut query = neo4rs::Query::new(query); + let person = Person::new("Alice"); + person.add_values_to_params(query); + + // A shorter way to do the same thing: + let query = person.create(); +} diff --git a/lib/include/exec/create_and_read_node.rs b/lib/include/exec/create_and_read_node.rs new file mode 100644 index 0000000..f8d63b0 --- /dev/null +++ b/lib/include/exec/create_and_read_node.rs @@ -0,0 +1,26 @@ +{ + #[derive(Node)] + struct Person { + id: String, + name: String, + } + + let person = Person::new("1234", "Alice"); + + // CREATE (n:Person { id: $id, name: $name }) + // $id: "1234" + // $name: "Alice" + graph.run(person.create()).await.unwrap(); + + // Find an existing person by id. + let id = PersonId::new("1234"); + + // MATCH (n:Person { id: $id }) RETURN n + // $id: "1234" + let mut stream = graph.execute(id.read()).await.unwrap(); + + let row = stream.next().await.unwrap().unwrap(); + let node: neo4rs::Node = row.get("n").unwrap(); + let person = Person::try_from(node).unwrap(); + assert_eq!(person.name(), "Alice"); +} diff --git a/lib/include/exec/create_and_read_relation.rs b/lib/include/exec/create_and_read_relation.rs new file mode 100644 index 0000000..1e1acce --- /dev/null +++ b/lib/include/exec/create_and_read_relation.rs @@ -0,0 +1,40 @@ +{ + #[derive(Relation)] + struct Knows { + since: u16, + } + + #[derive(Node)] + struct Person { + name: String, + } + + let alice = Person::new("Alice"); + let knows = Knows::new(2017); + let bob = Person::new("Bob"); + + // CREATE (s:Person { name: $s_name }) + // CREATE (e:Person { name: $e_name }) + // CREATE (s)-[r:KNOWS { since: $since }]->(e) + // + // $s_name: "Alice" + // $e_name: "Bob" + // $since: 2017 + let query = knows.create(RelationBound::Create(&alice), RelationBound::Create(&bob)); + graph.run(query).await.unwrap(); + + // Find the relationship just created. + let id = KnowsId::new(); + + // MATCH (s:Person { name: $s_name })-[r:KNOWS]-(e:Person { name: $e_name }) RETURN r + // + // $s_name: "Alice" + // $e_name: "Bob" + let query = id.read_between(&alice.into(), &bob.into()); + let mut stream = graph.execute(id.read()).await.unwrap(); + + let row = stream.next().await.unwrap().unwrap(); + let relation: neo4rs::UnboundedRelation = row.get("r").unwrap(); + let knows = Knows::try_from(relation).unwrap(); + assert_eq!(knows.since(), 2017); +} diff --git a/lib/include/id.rs b/lib/include/id.rs new file mode 100644 index 0000000..a860166 --- /dev/null +++ b/lib/include/id.rs @@ -0,0 +1,10 @@ +{ + #[derive(Node)] + struct Person { + #[id] + ssn: String, + name: String, + } + assert_eq!(Person::as_query_obj(), "Person { ssn: $ssn, name: $name }"); + assert_eq!(PersonId::as_query_obj(), "Person { ssn: $ssn }"); +} diff --git a/lib/include/id_inferred.rs b/lib/include/id_inferred.rs new file mode 100644 index 0000000..75a1822 --- /dev/null +++ b/lib/include/id_inferred.rs @@ -0,0 +1,9 @@ +{ + #[derive(Node)] + struct Person { + id: String, + name: String, + } + assert_eq!(Person::as_query_obj(), "Person { id: $id, name: $name }"); + assert_eq!(PersonId::as_query_obj(), "Person { id: $id }"); +} diff --git a/lib/include/id_multi.rs b/lib/include/id_multi.rs new file mode 100644 index 0000000..b7c0613 --- /dev/null +++ b/lib/include/id_multi.rs @@ -0,0 +1,11 @@ +{ + #[derive(Node)] + struct Person { + #[id] + name: String, + #[id] + address: String, + } + + assert_eq!(PersonId::as_query_obj(), "Person { name: $name, address: $address }"); +} diff --git a/lib/include/read_query.rs b/lib/include/read_query.rs new file mode 100644 index 0000000..ed1c462 --- /dev/null +++ b/lib/include/read_query.rs @@ -0,0 +1,19 @@ +{ + #[derive(Node)] + struct Person { + id: String, + name: String, + } + + // Query string: + let query = format!("MATCH (n:{}) RETURN n", PersonId::as_query_obj()); + assert_eq!(query, "MATCH (n:Person { id: $id }) RETURN n"); + + // Using [neo4rs::Query] manually: + let mut query = neo4rs::Query::new(query); + let id = PersonId::new("1234"); + id.add_values_to_params(query); + + // A shorter way to do the same thing: + let query = id.read(); +} diff --git a/lib/include/relationships.rs b/lib/include/relationships.rs new file mode 100644 index 0000000..41bb809 --- /dev/null +++ b/lib/include/relationships.rs @@ -0,0 +1,44 @@ +{ + #[derive(Relation)] + struct Knows { + since: u16, + } + assert_eq!(Knows::typename(), "KNOWS"); + assert_eq!(Knows::field_names(), ["since"]); + assert_eq!(Knows::as_query_fields(), "since: $since"); + assert_eq!(Knows::as_query_obj(), "KNOWS { since: $since }"); + + #[derive(Node)] + struct Person { + name: String, + } + + let alice = Person::new("Alice"); + let knows = Knows::new(2017); + let bob = Person::new("Bob"); + + // Create all three at once: + let query = knows.create(RelationBound::Create(&alice), RelationBound::Create(&bob)); + + // Or, manually build the same query: + let query = format!( + "CREATE (s:{}) \ + CREATE (e:{}) \ + CREATE (s)-[r:{}]->(e)", + Person::to_query_obj(Some("s"), StampMode::Create), + Knows::to_query_obj(None, StampMode::Create), + Person::to_query_obj(Some("e"), StampMode::Create), + ); + assert_eq!( + query, + "CREATE (s:Person { name: $s_name }) \ + CREATE (e:Person { name: $e_name }) \ + CREATE (s)-[:KNOWS { since: $since }]->(e)" + ); + + // Use it in a [neo4rs::Query]: + let mut query = neo4rs::Query::new(query); + alice.add_values_to_params(query, Some("s"), StampMode::Create); // Adds "s_name" to params + knows.add_values_to_params(query, None, StampMode::Create); // Adds "since" to params + bob.add_values_to_params(query, Some("e"), StampMode::Create); // Adds "e_name" to params +} diff --git a/lib/include/rename.rs b/lib/include/rename.rs new file mode 100644 index 0000000..e441fcf --- /dev/null +++ b/lib/include/rename.rs @@ -0,0 +1,12 @@ +{ + #[derive(Node)] + #[name = "Person2"] + struct Person { + #[name = "name2"] + name: String, + } + assert_eq!(Person::typename(), "Person2"); + assert_eq!(Person::field_names(), ["name2"]); + assert_eq!(Person::as_query_fields(), "name2: $name2"); + assert_eq!(Person::as_query_obj(), "Person2 { name2: $name2 }"); +} diff --git a/lib/include/static_strings.rs b/lib/include/static_strings.rs new file mode 100644 index 0000000..86fbc07 --- /dev/null +++ b/lib/include/static_strings.rs @@ -0,0 +1,10 @@ +{ + #[derive(Node)] + struct Person { + name: String, + } + assert_eq!(Person::typename(), "Person"); + assert_eq!(Person::field_names(), ["name"]); + assert_eq!(Person::as_query_fields(), "name: $name"); + assert_eq!(Person::as_query_obj(), "Person { name: $name }"); +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2950834..e488c0c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -7,6 +7,9 @@ //! an [dto::Identifier]. Identifiers are also DTOs, but they cannot have an //! Identifier themselves. //! +#![doc = include_str!("../include/static_strings.rs")] +#![doc = include_str!("../include/rename.rs")] +//! //! The `derive` macro will implement [dto::WithId] on the attributed struct, //! and create a [dto::Identifier] struct named e.g. `FooId`. The ID struct will use the //! same [typename()] as the attributed struct, and include any fields that are From 363a6f54852fd356d6e814de39f97f69d1f1b6d5 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 24 Aug 2023 18:09:24 -0400 Subject: [PATCH 17/24] fix var used --- lib/include/exec/create_and_read_node.rs | 2 ++ lib/include/exec/create_and_read_relation.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/include/exec/create_and_read_node.rs b/lib/include/exec/create_and_read_node.rs index f8d63b0..0229111 100644 --- a/lib/include/exec/create_and_read_node.rs +++ b/lib/include/exec/create_and_read_node.rs @@ -8,6 +8,7 @@ let person = Person::new("1234", "Alice"); // CREATE (n:Person { id: $id, name: $name }) + // // $id: "1234" // $name: "Alice" graph.run(person.create()).await.unwrap(); @@ -16,6 +17,7 @@ let id = PersonId::new("1234"); // MATCH (n:Person { id: $id }) RETURN n + // // $id: "1234" let mut stream = graph.execute(id.read()).await.unwrap(); diff --git a/lib/include/exec/create_and_read_relation.rs b/lib/include/exec/create_and_read_relation.rs index 1e1acce..0226581 100644 --- a/lib/include/exec/create_and_read_relation.rs +++ b/lib/include/exec/create_and_read_relation.rs @@ -31,7 +31,7 @@ // $s_name: "Alice" // $e_name: "Bob" let query = id.read_between(&alice.into(), &bob.into()); - let mut stream = graph.execute(id.read()).await.unwrap(); + let mut stream = graph.execute(query).await.unwrap(); let row = stream.next().await.unwrap().unwrap(); let relation: neo4rs::UnboundedRelation = row.get("r").unwrap(); From c55ae3f4b5924eec8c842a6035d65be6a687393e Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:36:44 -0500 Subject: [PATCH 18/24] upgrade and sort deps --- Cargo.lock | 274 +++++++++++++++++++++++++++++++++++---------- Cargo.toml | 2 +- example/Cargo.toml | 9 +- lib/Cargo.toml | 14 +-- 4 files changed, 227 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ad82c1..c56e318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -96,11 +96,10 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.41.0" +version = "1.42.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2f2e73fffe9455141e170fb9c1feb0ac521ec7e7dcd47a7cab72a658490fb8" +checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" dependencies = [ - "chrono", "serde", "serde_with", ] @@ -113,9 +112,12 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -145,6 +147,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "chrono-tz" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -156,9 +180,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -186,11 +210,11 @@ dependencies = [ "chrono", "cypher-dto-macros", "lenient_semver", - "neo4j_testcontainers", "neo4rs", "pretty_env_logger", "serde", "testcontainers", + "testcontainers-modules", "thiserror", "tokio", "uuid", @@ -203,7 +227,7 @@ dependencies = [ "convert_case", "quote", "serde", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -260,6 +284,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" +[[package]] +name = "delegate" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee5df75c70b95bd3aacc8e2fd098797692fb1d54121019c4de481e42f04c8a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -312,10 +347,10 @@ dependencies = [ "chrono", "cypher-dto", "lenient_semver", - "neo4j_testcontainers", "neo4rs", "pretty_env_logger", "testcontainers", + "testcontainers-modules", "tokio", "uuid", ] @@ -391,7 +426,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -480,16 +515,16 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -637,27 +672,22 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "neo4j_testcontainers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f502cf0e7658163604bf3ac7975f642d8a6c2c1f4110509493dc1d35dc70b0f7" -dependencies = [ - "testcontainers", -] - [[package]] name = "neo4rs" -version = "0.7.0-alpha.1" -source = "git+https://github.com/neo4j-labs/neo4rs.git?rev=0bd8099017d3ad57a844c9a456e7aaaca5e4721e#0bd8099017d3ad57a844c9a456e7aaaca5e4721e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1013d61f78c55571b95f0f2bad58dce2f5a346227e21271e8e5641f4d8e5919" dependencies = [ "async-trait", "bytes", "chrono", + "chrono-tz", "deadpool", + "delegate", "futures", "log", "neo4rs-macros", + "paste", "pin-project-lite", "serde", "thiserror", @@ -669,11 +699,12 @@ dependencies = [ [[package]] name = "neo4rs-macros" -version = "0.2.1" -source = "git+https://github.com/neo4j-labs/neo4rs.git?rev=0bd8099017d3ad57a844c9a456e7aaaca5e4721e#0bd8099017d3ad57a844c9a456e7aaaca5e4721e" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a0d57c55d2d1dc62a2b1d16a0a1079eb78d67c36bdf468d582ab4482ec7002" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -730,15 +761,68 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -769,18 +853,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -955,22 +1039,22 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.174" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -1026,6 +1110,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.8" @@ -1082,9 +1172,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -1102,9 +1192,9 @@ dependencies = [ [[package]] name = "testcontainers" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e2b1567ca8a2b819ea7b28c92be35d9f76fb9edb214321dcc86eb96023d1f87" +checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" dependencies = [ "bollard-stubs", "futures", @@ -1117,6 +1207,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "testcontainers-modules" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c391cd115649a8a14e5638d0606648d5348b216700a31f402987f57e58693766" +dependencies = [ + "testcontainers", +] + [[package]] name = "thiserror" version = "1.0.43" @@ -1134,7 +1233,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -1191,7 +1290,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", ] [[package]] @@ -1302,7 +1401,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -1324,7 +1423,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1386,12 +1485,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", ] [[package]] @@ -1400,7 +1499,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -1409,13 +1508,28 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -1424,38 +1538,80 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/Cargo.toml b/Cargo.toml index e88ee23..bf7ce62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = [ + "example", "lib", "macros", - "example", ] diff --git a/example/Cargo.toml b/example/Cargo.toml index 7959d1d..3e48817 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -4,15 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -chrono = "0.4.26" +chrono = "0.4" cypher-dto = { path = "../lib" } -# 0.7.0-alpha.1 -neo4rs = { git = "https://github.com/neo4j-labs/neo4rs.git", rev = "0bd8099017d3ad57a844c9a456e7aaaca5e4721e" } +neo4rs = "0.7.1" [dev-dependencies] lenient_semver = { version = "0.4.2", features = ["version_lite"] } -neo4j_testcontainers = "0.1.0" pretty_env_logger = "0.5.0" -testcontainers = "0.14.0" +testcontainers = "0.15.0" +testcontainers-modules = { version = "0.3.4", features = ["neo4j"] } tokio = "1.29.1" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 5df4b89..620ff49 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" keywords = ["neo4j", "cypher", "dto", "query", "graph"] categories = ["database"] repository = "https://github.com/jifalops/cypher-dto" +metadata.msrv = "1.60" [features] default = ["macros"] @@ -14,17 +15,16 @@ macros = ["cypher-dto-macros"] serde = ["macros", "cypher-dto-macros/serde"] [dependencies] -chrono = "0.4" +chrono = { version = "0.4" } cypher-dto-macros = { version = "0.2.0", path = "../macros", optional = true } -# neo4rs = "0.6.2" -neo4rs = { version = "0.7.0-alpha.1", git = "https://github.com/neo4j-labs/neo4rs.git", rev = "0bd8099017d3ad57a844c9a456e7aaaca5e4721e" } +neo4rs = "0.7.1" thiserror = "1.0" [dev-dependencies] -tokio = "1.29.1" lenient_semver = { version = "0.4.2", features = ["version_lite"] } -neo4j_testcontainers = "0.1.0" pretty_env_logger = "0.5.0" -serde = { version = "1.0.174", features = ["derive"] } -testcontainers = "0.14.0" +serde = { version = "1.0", features = ["derive"] } +testcontainers = "0.15.0" +testcontainers-modules = { version = "0.3.4", features = ["neo4j"] } +tokio = "1.29.1" uuid = { version = "1.4.1", features = ["v4"] } From aa3c068a0773e309f75bd5a18adfb04d87afa71e Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:38:08 -0500 Subject: [PATCH 19/24] update devcontainer to use my prebuilt rust image --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 7 ++----- .devcontainer/docker-compose.yml | 5 +++++ .devcontainer/post-create.sh | 10 ++-------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8ec05c1..965dd2f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM mcr.microsoft.com/devcontainers/rust:bookworm +FROM jifalops/rust diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index caf4140..dbc5314 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,16 +3,13 @@ "dockerComposeFile": "./docker-compose.yml", "service": "app", "workspaceFolder": "/cypher-dto", + "remoteUser": "developer", "postCreateCommand": ".devcontainer/post-create.sh", - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-in-docker:2": {} - }, "customizations": { "vscode": { "extensions": [ "rust-lang.rust-analyzer", - "JScearcy.rust-doc-viewer", + "serayuzgur.crates", "tamasfe.even-better-toml" ] } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 3db83cf..39a5f04 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -11,7 +11,12 @@ services: volumes: - ..:/cypher-dto:cached environment: + PROJECT_ROOT: /cypher-dto GITHUB_TOKEN: ${GITHUB_TOKEN} + TZ: ${TZ} + NEO4J_TEST_URI: "bolt://db:7687" + NEO4J_TEST_USER: "neo4j" + NEO4J_TEST_PASS: "developer" db: image: neo4j:5 diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 029d131..0af1ca9 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -1,14 +1,8 @@ # Ensure devcontainer user owns the project directory -sudo chown -R vscode:vscode /cypher-dto +sudo chown -R developer:developer $PROJECT_ROOT # Remember history on the local machine -ln -s /cypher-dto/.devcontainer/.bash_history ~/.bash_history +ln -s $PROJECT_ROOT/.devcontainer/.bash_history ~/.bash_history # Install dotfiles gh repo clone dotfiles ~/dotfiles && ~/dotfiles/install.sh - -# rustup and cargo bash completion. -sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends bash-completion \ - && mkdir -p ~/.local/share/bash-completion/completions \ - && rustup completions bash > ~/.local/share/bash-completion/completions/rustup \ - && rustup completions bash cargo > ~/.local/share/bash-completion/completions/cargo From 0cb7aa5ee3bef2832f6c1ab385d53737c1771a69 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:38:41 -0500 Subject: [PATCH 20/24] update scripts --- fix.sh | 7 +++++++ reset-test-database.sh | 4 ++++ test-fast.sh | 5 ----- test-isolated.sh | 4 ++++ test.sh | 2 ++ 5 files changed, 17 insertions(+), 5 deletions(-) create mode 100755 fix.sh create mode 100755 reset-test-database.sh delete mode 100755 test-fast.sh create mode 100755 test-isolated.sh create mode 100755 test.sh diff --git a/fix.sh b/fix.sh new file mode 100755 index 0000000..ab4e11b --- /dev/null +++ b/fix.sh @@ -0,0 +1,7 @@ +set -e +cargo sort +cargo sort lib +cargo sort macros +cargo fmt +cargo fix --allow-dirty +cargo clippy --fix --allow-dirty diff --git a/reset-test-database.sh b/reset-test-database.sh new file mode 100755 index 0000000..21896c9 --- /dev/null +++ b/reset-test-database.sh @@ -0,0 +1,4 @@ +curl -H accept:application/json -H content-type:application/json \ + "http://$NEO4J_TEST_USER:$NEO4J_TEST_PASS@db:7474/db/neo4j/tx/commit" \ + -d '{"statements": [{"statement": "MATCH (n) DETACH DELETE n;"}]}' +echo diff --git a/test-fast.sh b/test-fast.sh deleted file mode 100755 index d9f8d41..0000000 --- a/test-fast.sh +++ /dev/null @@ -1,5 +0,0 @@ -# Test on a running Neo4j instance instead of spinning up a container for each test. -export NEO4J_TEST_URI="bolt://db:7687" -export NEO4J_TEST_USER="neo4j" -export NEO4J_TEST_PASS="developer" -cargo test diff --git a/test-isolated.sh b/test-isolated.sh new file mode 100755 index 0000000..9133f10 --- /dev/null +++ b/test-isolated.sh @@ -0,0 +1,4 @@ +unset NEO4J_TEST_URI +unset NEO4J_TEST_USER +unset NEO4J_TEST_PASS +./test.sh diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..4f9c917 --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +cargo test --doc +cargo nextest run From 7306862783475b5c9bb4339fd54fa36980dfd1b4 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:40:22 -0500 Subject: [PATCH 21/24] fix neo4rs usage (breaking in 0.7), run ./fix.sh --- example/tests/basic_crud.rs | 2 +- example/tests/common/container.rs | 140 ++++++++++++++----- lib/src/entity.rs | 88 ++++++------ lib/src/lib.rs | 4 +- lib/src/stamps.rs | 8 +- lib/tests/basic_crud.rs | 2 +- lib/tests/common/container.rs | 140 ++++++++++++++----- lib/tests/common/fixtures/company.rs | 16 +-- lib/tests/common/fixtures/person.rs | 20 +-- macros/src/derive/entity/builder.rs | 1 - macros/src/derive/entity/field/map_helper.rs | 14 +- 11 files changed, 291 insertions(+), 144 deletions(-) diff --git a/example/tests/basic_crud.rs b/example/tests/basic_crud.rs index 8c41344..a07b603 100644 --- a/example/tests/basic_crud.rs +++ b/example/tests/basic_crud.rs @@ -59,7 +59,7 @@ async fn basic_crud() { graph.run(acme.identifier().delete()).await.unwrap(); let mut stream = graph - .execute(Query::new(format!("MATCH (n:Company) RETURN n"))) + .execute(Query::new("MATCH (n:Company) RETURN n".to_string())) .await .unwrap(); let row = stream.next().await.unwrap(); diff --git a/example/tests/common/container.rs b/example/tests/common/container.rs index f0114fa..fe01015 100644 --- a/example/tests/common/container.rs +++ b/example/tests/common/container.rs @@ -2,19 +2,52 @@ //! //! It also supports connecting to a real server using environment variables. //! -//! [Original source](https://github.com/neo4j-labs/neo4rs/blob/f1db22cab08c1f911876da43effc61a207828d85/lib/tests/container.rs) +//! [Original source](https://github.com/neo4j-labs/neo4rs/blob/ec0261895f56e476f4f1eb9c6a2151c7b945d454/lib/tests/container.rs) use lenient_semver::Version; -use neo4j_testcontainers::Neo4j; use neo4rs::{ConfigBuilder, Graph}; -use testcontainers::{clients::Cli, Container}; +use testcontainers::{clients::Cli, Container, RunnableImage}; +use testcontainers_modules::neo4j::{Neo4j, Neo4jImage}; -use std::sync::Arc; +use std::{error::Error, io::BufRead as _}; + +#[allow(dead_code)] +#[derive(Default)] +pub struct Neo4jContainerBuilder { + enterprise: bool, + config: ConfigBuilder, +} + +#[allow(dead_code)] +impl Neo4jContainerBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn with_enterprise_edition(mut self) -> Self { + self.enterprise = true; + self + } + + pub fn with_config(mut self, config: ConfigBuilder) -> Self { + self.config = config; + self + } + + pub fn modify_config(mut self, block: impl FnOnce(ConfigBuilder) -> ConfigBuilder) -> Self { + self.config = block(self.config); + self + } + + pub async fn start(self) -> Result> { + Neo4jContainer::from_config_and_edition(self.config, self.enterprise).await + } +} pub struct Neo4jContainer { - graph: Arc, + graph: Graph, version: String, - _container: Option>, + _container: Option>, } impl Neo4jContainer { @@ -24,31 +57,36 @@ impl Neo4jContainer { } pub async fn from_config(config: ConfigBuilder) -> Self { + Self::from_config_and_edition(config, false).await.unwrap() + } + + pub async fn from_config_and_edition( + config: ConfigBuilder, + enterprise_edition: bool, + ) -> Result> { let _ = pretty_env_logger::try_init(); + let connection = Self::create_test_endpoint(); let server = Self::server_from_env(); - let (connection, _container) = match server { + let (uri, _container) = match server { TestServer::TestContainer => { - let (connection, container) = Self::create_testcontainer(); - (connection, Some(container)) - } - TestServer::External(uri) => { - let connection = Self::create_test_endpoint(uri); - (connection, None) + let (uri, container) = Self::create_testcontainer(&connection, enterprise_edition)?; + (uri, Some(container)) } + TestServer::External(uri) => (uri, None), }; let version = connection.version; - let graph = Self::connect(config, connection.uri, &connection.auth).await; - Self { + let graph = Self::connect(config, uri, &connection.auth).await; + Ok(Self { graph, version, _container, - } + }) } - pub fn graph(&self) -> Arc { + pub fn graph(&self) -> Graph { self.graph.clone() } @@ -70,24 +108,63 @@ impl Neo4jContainer { } } - fn create_testcontainer() -> (TestConnection, Container<'static, Neo4j>) { + fn create_testcontainer( + connection: &TestConnection, + enterprise: bool, + ) -> Result<(String, Container<'static, Neo4jImage>), Box> + { + let image = Neo4j::new() + .with_user(connection.auth.user.to_owned()) + .with_password(connection.auth.pass.to_owned()); + let docker = Cli::default(); let docker = Box::leak(Box::new(docker)); - let container = docker.run(Neo4j::default()); + let container = if enterprise { + const ACCEPTANCE_FILE_NAME: &str = "container-license-acceptance.txt"; + + let version = format!("{}-enterprise", connection.version); + let image_name = format!("neo4j:{}", version); + + let acceptance_file = std::env::current_dir() + .ok() + .map(|o| o.join(ACCEPTANCE_FILE_NAME)); + + let has_license_acceptance = acceptance_file + .as_deref() + .and_then(|o| std::fs::File::open(o).ok()) + .into_iter() + .flat_map(|o| std::io::BufReader::new(o).lines()) + .any(|o| o.map_or(false, |line| line.trim() == image_name)); + + if !has_license_acceptance { + return Err(format!( + concat!( + "You need to accept the Neo4j Enterprise Edition license by ", + "creating the file `{}` with the following content:\n\n\t{}", + ), + acceptance_file.map_or_else( + || ACCEPTANCE_FILE_NAME.to_owned(), + |o| { o.display().to_string() } + ), + image_name + ) + .into()); + } + let image: RunnableImage = image.with_version(version).into(); + let image = image.with_env_var(("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")); - let uri = Neo4j::uri_ipv4(&container); - let version = container.image().version().to_owned(); - let user = container.image().user().to_owned(); - let pass = container.image().pass().to_owned(); - let auth = TestAuth { user, pass }; + docker.run(image) + } else { + docker.run(image.with_version(connection.version.to_owned())) + }; - let connection = TestConnection { uri, version, auth }; + let uri = format!("bolt://127.0.0.1:{}", container.image().bolt_port_ipv4()); - (connection, container) + Ok((uri, container)) } - fn create_test_endpoint(uri: String) -> TestConnection { + fn create_test_endpoint() -> TestConnection { const USER_VAR: &str = "NEO4J_TEST_USER"; const PASS_VAR: &str = "NEO4J_TEST_PASS"; const VERSION_VAR: &str = "NEO4J_VERSION_TAG"; @@ -103,10 +180,10 @@ impl Neo4jContainer { let auth = TestAuth { user, pass }; let version = var(VERSION_VAR).unwrap_or_else(|_| DEFAULT_VERSION_TAG.to_owned()); - TestConnection { uri, auth, version } + TestConnection { auth, version } } - async fn connect(config: ConfigBuilder, uri: String, auth: &TestAuth) -> Arc { + async fn connect(config: ConfigBuilder, uri: String, auth: &TestAuth) -> Graph { let config = config .uri(uri) .user(&auth.user) @@ -114,9 +191,7 @@ impl Neo4jContainer { .build() .unwrap(); - let graph = Graph::connect(config).await.unwrap(); - - Arc::new(graph) + Graph::connect(config).await.unwrap() } } @@ -126,7 +201,6 @@ struct TestAuth { } struct TestConnection { - uri: String, version: String, auth: TestAuth, } diff --git a/lib/src/entity.rs b/lib/src/entity.rs index b3e827c..0252eb7 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -127,11 +127,11 @@ pub(crate) mod tests { Ok(Self { name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, age: u8::try_from( value .get::("age") - .ok_or(Error::MissingField("age".to_owned()))?, + .map_err(|e| Error::MissingField("age".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("age".to_owned()))?, }) @@ -535,169 +535,169 @@ pub(crate) mod tests { usize_num: usize::try_from( value .get::("usize_num") - .ok_or(Error::MissingField("usize_num".to_owned()))?, + .map_err(|e| Error::MissingField("usize_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("usize_num".to_owned()))?, isize_num: isize::try_from( value .get::("isize_num") - .ok_or(Error::MissingField("isize_num".to_owned()))?, + .map_err(|e| Error::MissingField("isize_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("isize_num".to_owned()))?, u8_num: u8::try_from( value .get::("u8_num") - .ok_or(Error::MissingField("u8_num".to_owned()))?, + .map_err(|e| Error::MissingField("u8_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u8_num".to_owned()))?, u16_num: u16::try_from( value .get::("u16_num") - .ok_or(Error::MissingField("u16_num".to_owned()))?, + .map_err(|e| Error::MissingField("u16_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u16_num".to_owned()))?, u32_num: u32::try_from( value .get::("u32_num") - .ok_or(Error::MissingField("u32_num".to_owned()))?, + .map_err(|e| Error::MissingField("u32_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u32_num".to_owned()))?, u64_num: u64::try_from( value .get::("u64_num") - .ok_or(Error::MissingField("u64_num".to_owned()))?, + .map_err(|e| Error::MissingField("u64_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u64_num".to_owned()))?, u128_num: u128::try_from( value .get::("u128_num") - .ok_or(Error::MissingField("u128_num".to_owned()))?, + .map_err(|e| Error::MissingField("u128_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u128_num".to_owned()))?, i8_num: i8::try_from( value .get::("i8_num") - .ok_or(Error::MissingField("i8_num".to_owned()))?, + .map_err(|e| Error::MissingField("i8_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i8_num".to_owned()))?, i16_num: i16::try_from( value .get::("i16_num") - .ok_or(Error::MissingField("i16_num".to_owned()))?, + .map_err(|e| Error::MissingField("i16_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i16_num".to_owned()))?, i32_num: i32::try_from( value .get::("i32_num") - .ok_or(Error::MissingField("i32_num".to_owned()))?, + .map_err(|e| Error::MissingField("i32_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i32_num".to_owned()))?, i64_num: value .get("i64_num") - .ok_or(Error::MissingField("i64_num".to_owned()))?, + .map_err(|e| Error::MissingField("i64_num".to_owned()))?, i128_num: i128::try_from( value .get::("i128_num") - .ok_or(Error::MissingField("i128_num".to_owned()))?, + .map_err(|e| Error::MissingField("i128_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i128_num".to_owned()))?, f32_num: value .get::("f32_num") - .ok_or(Error::MissingField("f32_num".to_owned()))? + .map_err(|e| Error::MissingField("f32_num".to_owned()))? as f32, f64_num: value .get("f64_num") - .ok_or(Error::MissingField("f64_num".to_owned()))?, + .map_err(|e| Error::MissingField("f64_num".to_owned()))?, usize_opt: match value.get::("usize_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("usize_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, isize_opt: match value.get::("isize_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("isize_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, u8_opt: match value.get::("u8_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("u8_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, u16_opt: match value.get::("u16_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("u16_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, u32_opt: match value.get::("u32_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("u32_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, u64_opt: match value.get::("u64_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("u64_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, u128_opt: match value.get::("u128_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("u128_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, i8_opt: match value.get::("i8_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("i8_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, i16_opt: match value.get::("i16_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("i16_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, i32_opt: match value.get::("i32_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("i32_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, i64_opt: match value.get("i64_opt") { - Some(v) => Some(v), - None => None, + Ok(v) => Some(v), + Err(_) => None, }, i128_opt: match value.get::("i128_opt") { - Some(v) => Some( + Ok(v) => Some( v.try_into() .map_err(|_| Error::TypeMismatch("i128_opt".to_owned()))?, ), - None => None, + Err(_) => None, }, f32_opt: match value.get::("f32_opt") { - Some(v) => Some(v as f32), - None => None, + Ok(v) => Some(v as f32), + Err(_) => None, }, f64_opt: match value.get("f64_opt") { - Some(v) => Some(v), - None => None, + Ok(v) => Some(v), + Err(_) => None, }, }) } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e488c0c..73dc638 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -22,8 +22,8 @@ //! Dynamically added methods: //! 1. `fn into_values(self)` - returns a tuple of all the values in the struct. -#![warn(missing_docs)] -#![deny(rustdoc::broken_intra_doc_links)] +// #![warn(missing_docs)] +// #![deny(rustdoc::broken_intra_doc_links)] mod entity; mod error; diff --git a/lib/src/stamps.rs b/lib/src/stamps.rs index 55ca974..ffd597d 100644 --- a/lib/src/stamps.rs +++ b/lib/src/stamps.rs @@ -121,19 +121,19 @@ impl<'a> Neo4jMap<'a> { Neo4jMap::Row(value) => value .get::>(name) .map(|dt| dt.into()) - .ok_or(crate::Error::MissingField(name.to_owned())), + .map_err(|e| crate::Error::MissingField(name.to_owned())), Neo4jMap::Node(value) => value .get::>(name) .map(|dt| dt.into()) - .ok_or(crate::Error::MissingField(name.to_owned())), + .map_err(|e| crate::Error::MissingField(name.to_owned())), Neo4jMap::Relation(value) => value .get::>(name) .map(|dt| dt.into()) - .ok_or(crate::Error::MissingField(name.to_owned())), + .map_err(|e| crate::Error::MissingField(name.to_owned())), Neo4jMap::UnboundedRelation(value) => value .get::>(name) .map(|dt| dt.into()) - .ok_or(crate::Error::MissingField(name.to_owned())), + .map_err(|e| crate::Error::MissingField(name.to_owned())), } } } diff --git a/lib/tests/basic_crud.rs b/lib/tests/basic_crud.rs index 963b6b7..596d2f0 100644 --- a/lib/tests/basic_crud.rs +++ b/lib/tests/basic_crud.rs @@ -58,7 +58,7 @@ async fn basic_crud() { graph.run(acme.identifier().delete()).await.unwrap(); let mut stream = graph - .execute(Query::new(format!("MATCH (n:Company) RETURN n"))) + .execute(Query::new("MATCH (n:Company) RETURN n".to_string())) .await .unwrap(); let row = stream.next().await.unwrap(); diff --git a/lib/tests/common/container.rs b/lib/tests/common/container.rs index f0114fa..fe01015 100644 --- a/lib/tests/common/container.rs +++ b/lib/tests/common/container.rs @@ -2,19 +2,52 @@ //! //! It also supports connecting to a real server using environment variables. //! -//! [Original source](https://github.com/neo4j-labs/neo4rs/blob/f1db22cab08c1f911876da43effc61a207828d85/lib/tests/container.rs) +//! [Original source](https://github.com/neo4j-labs/neo4rs/blob/ec0261895f56e476f4f1eb9c6a2151c7b945d454/lib/tests/container.rs) use lenient_semver::Version; -use neo4j_testcontainers::Neo4j; use neo4rs::{ConfigBuilder, Graph}; -use testcontainers::{clients::Cli, Container}; +use testcontainers::{clients::Cli, Container, RunnableImage}; +use testcontainers_modules::neo4j::{Neo4j, Neo4jImage}; -use std::sync::Arc; +use std::{error::Error, io::BufRead as _}; + +#[allow(dead_code)] +#[derive(Default)] +pub struct Neo4jContainerBuilder { + enterprise: bool, + config: ConfigBuilder, +} + +#[allow(dead_code)] +impl Neo4jContainerBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn with_enterprise_edition(mut self) -> Self { + self.enterprise = true; + self + } + + pub fn with_config(mut self, config: ConfigBuilder) -> Self { + self.config = config; + self + } + + pub fn modify_config(mut self, block: impl FnOnce(ConfigBuilder) -> ConfigBuilder) -> Self { + self.config = block(self.config); + self + } + + pub async fn start(self) -> Result> { + Neo4jContainer::from_config_and_edition(self.config, self.enterprise).await + } +} pub struct Neo4jContainer { - graph: Arc, + graph: Graph, version: String, - _container: Option>, + _container: Option>, } impl Neo4jContainer { @@ -24,31 +57,36 @@ impl Neo4jContainer { } pub async fn from_config(config: ConfigBuilder) -> Self { + Self::from_config_and_edition(config, false).await.unwrap() + } + + pub async fn from_config_and_edition( + config: ConfigBuilder, + enterprise_edition: bool, + ) -> Result> { let _ = pretty_env_logger::try_init(); + let connection = Self::create_test_endpoint(); let server = Self::server_from_env(); - let (connection, _container) = match server { + let (uri, _container) = match server { TestServer::TestContainer => { - let (connection, container) = Self::create_testcontainer(); - (connection, Some(container)) - } - TestServer::External(uri) => { - let connection = Self::create_test_endpoint(uri); - (connection, None) + let (uri, container) = Self::create_testcontainer(&connection, enterprise_edition)?; + (uri, Some(container)) } + TestServer::External(uri) => (uri, None), }; let version = connection.version; - let graph = Self::connect(config, connection.uri, &connection.auth).await; - Self { + let graph = Self::connect(config, uri, &connection.auth).await; + Ok(Self { graph, version, _container, - } + }) } - pub fn graph(&self) -> Arc { + pub fn graph(&self) -> Graph { self.graph.clone() } @@ -70,24 +108,63 @@ impl Neo4jContainer { } } - fn create_testcontainer() -> (TestConnection, Container<'static, Neo4j>) { + fn create_testcontainer( + connection: &TestConnection, + enterprise: bool, + ) -> Result<(String, Container<'static, Neo4jImage>), Box> + { + let image = Neo4j::new() + .with_user(connection.auth.user.to_owned()) + .with_password(connection.auth.pass.to_owned()); + let docker = Cli::default(); let docker = Box::leak(Box::new(docker)); - let container = docker.run(Neo4j::default()); + let container = if enterprise { + const ACCEPTANCE_FILE_NAME: &str = "container-license-acceptance.txt"; + + let version = format!("{}-enterprise", connection.version); + let image_name = format!("neo4j:{}", version); + + let acceptance_file = std::env::current_dir() + .ok() + .map(|o| o.join(ACCEPTANCE_FILE_NAME)); + + let has_license_acceptance = acceptance_file + .as_deref() + .and_then(|o| std::fs::File::open(o).ok()) + .into_iter() + .flat_map(|o| std::io::BufReader::new(o).lines()) + .any(|o| o.map_or(false, |line| line.trim() == image_name)); + + if !has_license_acceptance { + return Err(format!( + concat!( + "You need to accept the Neo4j Enterprise Edition license by ", + "creating the file `{}` with the following content:\n\n\t{}", + ), + acceptance_file.map_or_else( + || ACCEPTANCE_FILE_NAME.to_owned(), + |o| { o.display().to_string() } + ), + image_name + ) + .into()); + } + let image: RunnableImage = image.with_version(version).into(); + let image = image.with_env_var(("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")); - let uri = Neo4j::uri_ipv4(&container); - let version = container.image().version().to_owned(); - let user = container.image().user().to_owned(); - let pass = container.image().pass().to_owned(); - let auth = TestAuth { user, pass }; + docker.run(image) + } else { + docker.run(image.with_version(connection.version.to_owned())) + }; - let connection = TestConnection { uri, version, auth }; + let uri = format!("bolt://127.0.0.1:{}", container.image().bolt_port_ipv4()); - (connection, container) + Ok((uri, container)) } - fn create_test_endpoint(uri: String) -> TestConnection { + fn create_test_endpoint() -> TestConnection { const USER_VAR: &str = "NEO4J_TEST_USER"; const PASS_VAR: &str = "NEO4J_TEST_PASS"; const VERSION_VAR: &str = "NEO4J_VERSION_TAG"; @@ -103,10 +180,10 @@ impl Neo4jContainer { let auth = TestAuth { user, pass }; let version = var(VERSION_VAR).unwrap_or_else(|_| DEFAULT_VERSION_TAG.to_owned()); - TestConnection { uri, auth, version } + TestConnection { auth, version } } - async fn connect(config: ConfigBuilder, uri: String, auth: &TestAuth) -> Arc { + async fn connect(config: ConfigBuilder, uri: String, auth: &TestAuth) -> Graph { let config = config .uri(uri) .user(&auth.user) @@ -114,9 +191,7 @@ impl Neo4jContainer { .build() .unwrap(); - let graph = Graph::connect(config).await.unwrap(); - - Arc::new(graph) + Graph::connect(config).await.unwrap() } } @@ -126,7 +201,6 @@ struct TestAuth { } struct TestConnection { - uri: String, version: String, auth: TestAuth, } diff --git a/lib/tests/common/fixtures/company.rs b/lib/tests/common/fixtures/company.rs index 726df93..56582f7 100644 --- a/lib/tests/common/fixtures/company.rs +++ b/lib/tests/common/fixtures/company.rs @@ -55,10 +55,10 @@ impl TryFrom for Company { Ok(Self { name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .ok_or(Error::MissingField("state".to_owned()))?, + .map_err(|e| Error::MissingField("state".to_owned()))?, created: map.get_timestamp("created")?, updated: map.get_timestamp("updated")?, }) @@ -80,10 +80,10 @@ impl TryFrom for Company { Ok(Self { name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .ok_or(Error::MissingField("state".to_owned()))?, + .map_err(|e| Error::MissingField("state".to_owned()))?, created: map.get_timestamp("created")?, updated: map.get_timestamp("updated")?, }) @@ -115,10 +115,10 @@ impl TryFrom for CompanyId { Ok(Self { name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .ok_or(Error::MissingField("state".to_owned()))?, + .map_err(|e| Error::MissingField("state".to_owned()))?, }) } } @@ -148,10 +148,10 @@ impl TryFrom for CompanyId { Ok(Self { name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .ok_or(Error::MissingField("state".to_owned()))?, + .map_err(|e| Error::MissingField("state".to_owned()))?, }) } } diff --git a/lib/tests/common/fixtures/person.rs b/lib/tests/common/fixtures/person.rs index 130348c..7ad81ed 100644 --- a/lib/tests/common/fixtures/person.rs +++ b/lib/tests/common/fixtures/person.rs @@ -84,16 +84,16 @@ impl TryFrom for Person { Ok(Self { id: value .get("id") - .ok_or(Error::MissingField("id".to_owned()))?, + .map_err(|e| Error::MissingField("id".to_owned()))?, name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, age: match value.get::("age") { - Some(age) => Some( + Ok(age) => Some( age.try_into() .map_err(|_| Error::TypeMismatch("age".to_owned()))?, ), - None => None, + Err(_) => None, }, created_at: Some(map.get_timestamp("created_at")?), updated_at: Some(map.get_timestamp("updated_at")?), @@ -115,16 +115,16 @@ impl TryFrom for Person { Ok(Self { id: value .get("id") - .ok_or(Error::MissingField("id".to_owned()))?, + .map_err(|e| Error::MissingField("id".to_owned()))?, name: value .get("name") - .ok_or(Error::MissingField("name".to_owned()))?, + .map_err(|e| Error::MissingField("name".to_owned()))?, age: match value.get::("age") { - Some(age) => Some( + Ok(age) => Some( age.try_into() .map_err(|_| Error::TypeMismatch("age".to_owned()))?, ), - None => None, + Err(_) => None, }, created_at: Some(map.get_timestamp("created_at")?), updated_at: Some(map.get_timestamp("updated_at")?), @@ -153,7 +153,7 @@ impl TryFrom for PersonId { Ok(Self { id: value .get("id") - .ok_or(Error::MissingField("id".to_owned()))?, + .map_err(|e| Error::MissingField("id".to_owned()))?, }) } } @@ -181,7 +181,7 @@ impl TryFrom for PersonId { Ok(Self { id: value .get("id") - .ok_or(Error::MissingField("id".to_owned()))?, + .map_err(|e| Error::MissingField("id".to_owned()))?, }) } } diff --git a/macros/src/derive/entity/builder.rs b/macros/src/derive/entity/builder.rs index 2a10460..97549f7 100644 --- a/macros/src/derive/entity/builder.rs +++ b/macros/src/derive/entity/builder.rs @@ -1,7 +1,6 @@ use super::{ArgHelper, Entity}; use quote::{__private::TokenStream, format_ident, quote}; - pub fn impl_builder(entity: &Entity) -> TokenStream { let entity_ident = entity.ident(); let _entity_name = entity.name(); diff --git a/macros/src/derive/entity/field/map_helper.rs b/macros/src/derive/entity/field/map_helper.rs index b9d08a7..58da3d9 100644 --- a/macros/src/derive/entity/field/map_helper.rs +++ b/macros/src/derive/entity/field/map_helper.rs @@ -10,27 +10,27 @@ pub fn field_from_boltmap(name: &str, typ: &FieldType) -> TokenStream { quote!( value.get::<::chrono::DateTime<::chrono::FixedOffset>>(#name) .map(|dt| dt.into()) - .ok_or(::cypher_dto::Error::MissingField(#name.to_owned()))? + .map_err(|e| ::cypher_dto::Error::MissingField(#name.to_owned()))? ) } FieldType::OptionDateTimeUtc(_ty) => { quote!( value.get::<::chrono::DateTime<::chrono::FixedOffset>>(#name) - .map(|dt| dt.into()) + .map(|dt| dt.into()).ok() ) } FieldType::Num(_ty, num) => { // Build from the inside out. // Example: cypher-dto/lib/src/entity.rs#L425 let mut tokens = quote!( - value.get(#name).ok_or(::cypher_dto::Error::MissingField(#name.to_owned()))? + value.get(#name).map_err(|e| ::cypher_dto::Error::MissingField(#name.to_owned()))? ); // Handle `value.get::()` if let Some(type_arg) = num.map_getter_type_arg() { let cast_type = type_arg.to_type(); tokens = quote!( - value.get::<#cast_type>(#name).ok_or(::cypher_dto::Error::MissingField(#name.to_owned()))? + value.get::<#cast_type>(#name).map_err(|e| ::cypher_dto::Error::MissingField(#name.to_owned()))? ); } @@ -111,8 +111,8 @@ pub fn field_from_boltmap(name: &str, typ: &FieldType) -> TokenStream { quote!( match #get_call { - Some(v) => Some(#some_inner), - None => None, + Ok(v) => Some(#some_inner), + Err(_) => None, } ) } @@ -123,7 +123,7 @@ pub fn field_from_boltmap(name: &str, typ: &FieldType) -> TokenStream { } FieldType::Other(_ty) => { quote!( - value.get(#name).ok_or(::cypher_dto::Error::MissingField(#name.to_owned()))? + value.get(#name).map_err(|e| ::cypher_dto::Error::MissingField(#name.to_owned()))? ) } } From 2b3f99b3f7b400f25c7c63ffe758ca7abc7902d7 Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:44:57 -0500 Subject: [PATCH 22/24] run ./fix.sh --- lib/Cargo.toml | 2 +- lib/src/entity.rs | 32 ++++++++++++++-------------- lib/src/stamps.rs | 8 +++---- lib/tests/common/fixtures/company.rs | 16 +++++++------- lib/tests/common/fixtures/person.rs | 12 +++++------ 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 620ff49..2f2f321 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT" keywords = ["neo4j", "cypher", "dto", "query", "graph"] categories = ["database"] repository = "https://github.com/jifalops/cypher-dto" -metadata.msrv = "1.60" +metadata = { msrv = "1.60" } [features] default = ["macros"] diff --git a/lib/src/entity.rs b/lib/src/entity.rs index 0252eb7..9a95ac5 100644 --- a/lib/src/entity.rs +++ b/lib/src/entity.rs @@ -127,11 +127,11 @@ pub(crate) mod tests { Ok(Self { name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, age: u8::try_from( value .get::("age") - .map_err(|e| Error::MissingField("age".to_owned()))?, + .map_err(|_e| Error::MissingField("age".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("age".to_owned()))?, }) @@ -535,79 +535,79 @@ pub(crate) mod tests { usize_num: usize::try_from( value .get::("usize_num") - .map_err(|e| Error::MissingField("usize_num".to_owned()))?, + .map_err(|_e| Error::MissingField("usize_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("usize_num".to_owned()))?, isize_num: isize::try_from( value .get::("isize_num") - .map_err(|e| Error::MissingField("isize_num".to_owned()))?, + .map_err(|_e| Error::MissingField("isize_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("isize_num".to_owned()))?, u8_num: u8::try_from( value .get::("u8_num") - .map_err(|e| Error::MissingField("u8_num".to_owned()))?, + .map_err(|_e| Error::MissingField("u8_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u8_num".to_owned()))?, u16_num: u16::try_from( value .get::("u16_num") - .map_err(|e| Error::MissingField("u16_num".to_owned()))?, + .map_err(|_e| Error::MissingField("u16_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u16_num".to_owned()))?, u32_num: u32::try_from( value .get::("u32_num") - .map_err(|e| Error::MissingField("u32_num".to_owned()))?, + .map_err(|_e| Error::MissingField("u32_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u32_num".to_owned()))?, u64_num: u64::try_from( value .get::("u64_num") - .map_err(|e| Error::MissingField("u64_num".to_owned()))?, + .map_err(|_e| Error::MissingField("u64_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u64_num".to_owned()))?, u128_num: u128::try_from( value .get::("u128_num") - .map_err(|e| Error::MissingField("u128_num".to_owned()))?, + .map_err(|_e| Error::MissingField("u128_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("u128_num".to_owned()))?, i8_num: i8::try_from( value .get::("i8_num") - .map_err(|e| Error::MissingField("i8_num".to_owned()))?, + .map_err(|_e| Error::MissingField("i8_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i8_num".to_owned()))?, i16_num: i16::try_from( value .get::("i16_num") - .map_err(|e| Error::MissingField("i16_num".to_owned()))?, + .map_err(|_e| Error::MissingField("i16_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i16_num".to_owned()))?, i32_num: i32::try_from( value .get::("i32_num") - .map_err(|e| Error::MissingField("i32_num".to_owned()))?, + .map_err(|_e| Error::MissingField("i32_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i32_num".to_owned()))?, i64_num: value .get("i64_num") - .map_err(|e| Error::MissingField("i64_num".to_owned()))?, + .map_err(|_e| Error::MissingField("i64_num".to_owned()))?, i128_num: i128::try_from( value .get::("i128_num") - .map_err(|e| Error::MissingField("i128_num".to_owned()))?, + .map_err(|_e| Error::MissingField("i128_num".to_owned()))?, ) .map_err(|_| Error::TypeMismatch("i128_num".to_owned()))?, f32_num: value .get::("f32_num") - .map_err(|e| Error::MissingField("f32_num".to_owned()))? + .map_err(|_e| Error::MissingField("f32_num".to_owned()))? as f32, f64_num: value .get("f64_num") - .map_err(|e| Error::MissingField("f64_num".to_owned()))?, + .map_err(|_e| Error::MissingField("f64_num".to_owned()))?, usize_opt: match value.get::("usize_opt") { Ok(v) => Some( diff --git a/lib/src/stamps.rs b/lib/src/stamps.rs index ffd597d..65fea42 100644 --- a/lib/src/stamps.rs +++ b/lib/src/stamps.rs @@ -121,19 +121,19 @@ impl<'a> Neo4jMap<'a> { Neo4jMap::Row(value) => value .get::>(name) .map(|dt| dt.into()) - .map_err(|e| crate::Error::MissingField(name.to_owned())), + .map_err(|_e| crate::Error::MissingField(name.to_owned())), Neo4jMap::Node(value) => value .get::>(name) .map(|dt| dt.into()) - .map_err(|e| crate::Error::MissingField(name.to_owned())), + .map_err(|_e| crate::Error::MissingField(name.to_owned())), Neo4jMap::Relation(value) => value .get::>(name) .map(|dt| dt.into()) - .map_err(|e| crate::Error::MissingField(name.to_owned())), + .map_err(|_e| crate::Error::MissingField(name.to_owned())), Neo4jMap::UnboundedRelation(value) => value .get::>(name) .map(|dt| dt.into()) - .map_err(|e| crate::Error::MissingField(name.to_owned())), + .map_err(|_e| crate::Error::MissingField(name.to_owned())), } } } diff --git a/lib/tests/common/fixtures/company.rs b/lib/tests/common/fixtures/company.rs index 56582f7..29831ec 100644 --- a/lib/tests/common/fixtures/company.rs +++ b/lib/tests/common/fixtures/company.rs @@ -55,10 +55,10 @@ impl TryFrom for Company { Ok(Self { name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .map_err(|e| Error::MissingField("state".to_owned()))?, + .map_err(|_e| Error::MissingField("state".to_owned()))?, created: map.get_timestamp("created")?, updated: map.get_timestamp("updated")?, }) @@ -80,10 +80,10 @@ impl TryFrom for Company { Ok(Self { name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .map_err(|e| Error::MissingField("state".to_owned()))?, + .map_err(|_e| Error::MissingField("state".to_owned()))?, created: map.get_timestamp("created")?, updated: map.get_timestamp("updated")?, }) @@ -115,10 +115,10 @@ impl TryFrom for CompanyId { Ok(Self { name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .map_err(|e| Error::MissingField("state".to_owned()))?, + .map_err(|_e| Error::MissingField("state".to_owned()))?, }) } } @@ -148,10 +148,10 @@ impl TryFrom for CompanyId { Ok(Self { name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, state: value .get("state") - .map_err(|e| Error::MissingField("state".to_owned()))?, + .map_err(|_e| Error::MissingField("state".to_owned()))?, }) } } diff --git a/lib/tests/common/fixtures/person.rs b/lib/tests/common/fixtures/person.rs index 7ad81ed..1c3d14d 100644 --- a/lib/tests/common/fixtures/person.rs +++ b/lib/tests/common/fixtures/person.rs @@ -84,10 +84,10 @@ impl TryFrom for Person { Ok(Self { id: value .get("id") - .map_err(|e| Error::MissingField("id".to_owned()))?, + .map_err(|_e| Error::MissingField("id".to_owned()))?, name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, age: match value.get::("age") { Ok(age) => Some( age.try_into() @@ -115,10 +115,10 @@ impl TryFrom for Person { Ok(Self { id: value .get("id") - .map_err(|e| Error::MissingField("id".to_owned()))?, + .map_err(|_e| Error::MissingField("id".to_owned()))?, name: value .get("name") - .map_err(|e| Error::MissingField("name".to_owned()))?, + .map_err(|_e| Error::MissingField("name".to_owned()))?, age: match value.get::("age") { Ok(age) => Some( age.try_into() @@ -153,7 +153,7 @@ impl TryFrom for PersonId { Ok(Self { id: value .get("id") - .map_err(|e| Error::MissingField("id".to_owned()))?, + .map_err(|_e| Error::MissingField("id".to_owned()))?, }) } } @@ -181,7 +181,7 @@ impl TryFrom for PersonId { Ok(Self { id: value .get("id") - .map_err(|e| Error::MissingField("id".to_owned()))?, + .map_err(|_e| Error::MissingField("id".to_owned()))?, }) } } From a5f17971643b70d10917b836444910af34512efa Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 16:47:22 -0500 Subject: [PATCH 23/24] prevent clippy from removing pub use that is actually used in the tests. --- lib/tests/common/fixtures/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tests/common/fixtures/mod.rs b/lib/tests/common/fixtures/mod.rs index 3a09e2e..3599489 100644 --- a/lib/tests/common/fixtures/mod.rs +++ b/lib/tests/common/fixtures/mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +#![allow(unused_imports)] //! Hand implementations for what the macros would generate. mod company; From cc1e871a9c75cc8832e1cd161e51d0c768fb14fb Mon Sep 17 00:00:00 2001 From: Jacob Phillips Date: Thu, 29 Feb 2024 19:03:04 -0500 Subject: [PATCH 24/24] need to add the 1.0 tag to dockerfile (no latest) --- .devcontainer/Dockerfile | 2 +- coverage.sh | 3 +-- doc.sh | 6 +----- test.sh | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 965dd2f..9485362 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM jifalops/rust +FROM jifalops/rust:1.0 diff --git a/coverage.sh b/coverage.sh index 97b5e94..bf4923b 100755 --- a/coverage.sh +++ b/coverage.sh @@ -1,5 +1,4 @@ #!/bin/bash set -e -PORT=${1:-8081} cargo llvm-cov test --html -python3 -m http.server $PORT --directory target/llvm-cov/html +python3 -m http.server --directory target/llvm-cov/html diff --git a/doc.sh b/doc.sh index 4f51a5a..e6d3fcc 100755 --- a/doc.sh +++ b/doc.sh @@ -1,8 +1,4 @@ #!/bin/bash set -e -PORT=${1:-8080} cargo doc --no-deps -echo "=================================" -echo "http://localhost:$PORT/cypher_dto" -echo "=================================" -python3 -m http.server $PORT --directory target/doc/ +python3 -m http.server --directory target/doc/ diff --git a/test.sh b/test.sh index 4f9c917..51cd30a 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1,2 @@ -cargo test --doc +cargo test --doc --no-fail-fast cargo nextest run