# 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/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 with Data Transfer Objects (DTOs) in Neo4j. ## Brief Overview ```rust use cypher_dto::{Node, Relation}; #[derive(Node)] struct Person { name: String } #[derive(Relation)] struct Knows { since: u16 } let alice = Person::new("Alice"); let bob = Person::new("Bob"); let knows = Knows::new(2020); let graph = neo4rs::Graph::new(/*...*/); let query: neo4rs::Query = alice.create(); graph.execute(query); let alice = alice.into_builder().name("Allison").build(); let query = alice.update(); graph.execute(query); let query = knows.create(RelationBound::Match(&alice), RelationBound::Create(&bob)); graph.execute(query); ``` ## Examples ### Basic usage ```rust use cypher_dto::Node; use neo4rs::Query; #[derive(Node)] struct Person { id: String, // Inferred to be the only #[id] field. name: String, #[name = "zip_code"] zip: String, } assert_eq!(Person::typename(), "Person"); assert_eq!(Person::field_names(), &["id", "name", "zip_code"]); // For building parameterized cypher queries... assert_eq!(Person::as_query_fields(), "id: $id, name: $name, zip_code: $zip_code"); assert_eq!(Person::as_query_obj(), "Person { id: $id, name: $name, zip_code: $zip_code }"); let person = Person::new("123", "John", "12345"); // Unitary CRUD operations are provided for convenience. let query = person.create(); // Equivalent to: let mut query = Query::new(format!( "CREATE (n:{})", Person::as_query_obj() )); query = person.add_values_to_params(query, None, StampMode::Create); ``` ```rust #[derive(Clone, Debug, PartialEq, Relation)] struct Knows; assert_eq!(Knows::typename(), "KNOWS"); ``` ### Multi valued identifiers ```rust use cypher_dto::Node; use neo4rs::Query; #[derive(Node)] struct Company { #[id] name: String, #[id] state: String, phone: String, } let company = Company::new("Acme", "CA", "555-555-5555"); let id = company.identifier(); assert_eq!(id.name(), "Acme"); assert_eq!(id.state(), "CA"); assert_eq!(id, CompanyId::new("Acme", "CA")); assert_eq!(CompanyId::typename(), "Company"); assert_eq!(CompanyId::field_names(), &["name", "state"]); let query = id.read(); // Equivalent to: let mut query = Query::new(format!( "MATCH (n:{}) RETURN n", CompanyId::as_query_obj() )); query = id.add_values_to_params(query, None, StampMode::Read); ``` ### Builder, new, and getters * The generated `::new()` method will accept `&str` for `String` fields, and `&[T]` for `Vec` fields. * Doc comments are copied to the getters for the struct, the getter(s) on the `FooId` struct, and the methods on the `FooBuilder` struct. ```rust #[derive(Node)] struct Person { /// This comment is copied to the getter, the Id getter, and the builder method. name: String, } let p = Person::new("John"); let p = p.into_builder().name("Ferris").build(); assert_eq!(p.name(), "Ferris"); ``` ### Timestamps There's built-in support for special timestamp fields: `created_at` and `updated_at`, `created` and `updated`, or any single one of those four. ```rust use cypher_dto::timestamps; #[timestamps] struct Person { name: String, } // Adds two fields: // created_at: Option>, // updated_at: Option>, #[timestamps = "short"] struct Person { name: String, } // Adds two fields: // created: Option>, // updated: Option>, #[timestamps = "updated_at"] struct Person { name: String, } // Adds one field: // updated_at: Option>, ``` The timestamp fields are treated a little bit differently than other fields: * They are not parameters in the generated `::new()` method. * They sometimes have hardcoded values in `::to_query_fields()`. * Calling `to_query_fields()` with `StampMode::Create` will use `datetime()` in the query instead of `$created_at` for example. `Option>` is used instead of `DateTime` so that the fields can be `None` when creating a new instance, before it exists in the database. For more details about the macro variations, see the [cypher-dto-macros](https://crates.io/crates/cypher-dto-macros) crate. ### Unitary CRUD operations This library takes the point of view that non-trivial queries should be managed by hand, but it does provide basic CRUD operations for convenience. `#[derive(Node)]` and `#[derive(Relation)]` structs get `create()` and `update()` methods, while the corresponding `FooId` structs get `read()` and `delete()` methods, all of which return a `neo4rs::Query`. None of those methods even take any arguments, with the exception of creating a relation, which needs to know if the start and end nodes it's between need created or already exist. ```rust use cypher_dto::{Node, Relation}; #[derive(Node)] Person { name: String, } #[derive(Clone, Debug, PartialEq, Relation)] struct Knows; let alice = Person::new("Alice"); let bob = Person::new("Bob"); let knows = Knows; // Relations can have fields and ids too. let query = knows.create(RelationBound::Create(&alice), RelationBound::Create(&bob)); ``` ### Node labels While [`neo4rs::Node::labels()`](https://docs.rs/neo4rs/latest/neo4rs/struct.Node.html#method.labels) can be used to read the current labels of a node from the database, this library provides a way to define the labels a struct should use. Those labels are then used by the built in CRUD operations. ```rust #[derive(Node)] #[labels("Person", "Employee")] Person { name: String, } let person = Person::new("Alice"); assert_eq!(person.labels(), &["Person", "Employee"]); ```