From 66974751f281d06278997904a251fb17f55bb5cb Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 13 Aug 2021 14:34:41 +0300 Subject: [PATCH 001/104] Basic project structure with Event and VersionedEvent impls --- Cargo.toml | 11 +++++ codegen/Cargo.toml | 7 ++++ codegen/README.md | 2 + codegen/impl/Cargo.toml | 15 +++++++ codegen/impl/README.md | 2 + codegen/impl/src/event/mod.rs | 79 +++++++++++++++++++++++++++++++++++ codegen/impl/src/lib.rs | 41 ++++++++++++++++++ codegen/src/lib.rs | 24 +++++++++++ core/Cargo.toml | 7 ++++ core/README.md | 2 + core/src/event/mod.rs | 47 +++++++++++++++++++++ core/src/lib.rs | 26 ++++++++++++ src/lib.rs | 27 ++++++++++++ 13 files changed, 290 insertions(+) create mode 100644 Cargo.toml create mode 100644 codegen/Cargo.toml create mode 100644 codegen/README.md create mode 100644 codegen/impl/Cargo.toml create mode 100644 codegen/impl/README.md create mode 100644 codegen/impl/src/event/mod.rs create mode 100644 codegen/impl/src/lib.rs create mode 100644 codegen/src/lib.rs create mode 100644 core/Cargo.toml create mode 100644 core/README.md create mode 100644 core/src/event/mod.rs create mode 100644 core/src/lib.rs create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8903e15 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arcana" +version = "0.1.0" +edition = "2018" + +[dependencies] +arcana-core = { version = "0.1.0", path = "./core" } +arcana-codegen = { version = "0.1.0", path = "./codegen" } + +[workspace] +members = ["codegen", "codegen/impl", "core"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..ace69e3 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arcana-codegen" +version = "0.1.0" +edition = "2018" + +[dependencies] +arcana-codegen-impl = { version = "0.1.0", path = "./impl"} diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 0000000..35c404e --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,2 @@ +arcana-codegen +============== diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml new file mode 100644 index 0000000..73995bd --- /dev/null +++ b/codegen/impl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "arcana-codegen-impl" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "1.0" +[dependencies.synthez] + git = "https://github.com/arcana-rs/synthez" + branch = "fix-raw-idents" diff --git a/codegen/impl/README.md b/codegen/impl/README.md new file mode 100644 index 0000000..594ebe3 --- /dev/null +++ b/codegen/impl/README.md @@ -0,0 +1,2 @@ +arcana-codegen-impl +=================== diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs new file mode 100644 index 0000000..daa6221 --- /dev/null +++ b/codegen/impl/src/event/mod.rs @@ -0,0 +1,79 @@ +//! TODO + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Result; +use synthez::ParseAttrs; + +/// Derives `serde::Deserialize` for `arcana::VersionedEvent`. +pub(crate) fn derive(input: TokenStream) -> Result { + let input: syn::DeriveInput = syn::parse2(input)?; + let attrs: Attrs = Attrs::parse_attrs("event", &input)?; + + match (attrs.r#type, attrs.version) { + (Some(event_type), Some(event_version)) => { + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + Ok(quote! { + #[automatically_derived] + impl #impl_generics ::arcana::VersionedEvent for + #name #ty_generics #where_clause + { + #[inline(always)] + fn event_type() -> &'static str { + #event_type + } + + #[inline(always)] + fn ver() -> u16 { + #event_version + } + } + }) + } + _ => Err(syn::Error::new_spanned( + input, + "`type` and `version` arguments expected", + )), + } +} + +#[derive(Default, ParseAttrs)] +struct Attrs { + #[parse(value)] + r#type: Option, + + #[parse(value)] + version: Option, +} + +#[cfg(test)] +mod spec { + use super::{derive, quote}; + + #[test] + fn derives_struct_impl() { + let input = syn::parse_quote! { + #[event(type = "event", version = 1)] + struct Event; + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::VersionedEvent for Event { + #[inline(always)] + fn event_type() -> &'static str { + "event" + } + + #[inline(always)] + fn ver() -> u16 { + 1 + } + } + }; + + assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + } +} diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs new file mode 100644 index 0000000..5ad4f02 --- /dev/null +++ b/codegen/impl/src/lib.rs @@ -0,0 +1,41 @@ +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] + +mod event; + +use proc_macro::TokenStream; + +/// Macro for deriving `arcana::VersionedEvent`. +/// +/// # Example +/// +/// ``` +/// #[derive(VersionedEvent)] +/// #[type = "event", version = 1] +/// struct Event; +/// ``` +#[proc_macro_derive(VersionedEvent, attributes(event))] +pub fn derive_versioned_event(input: TokenStream) -> TokenStream { + event::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..2de4abf --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,24 @@ +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] + +#[doc(inline)] +pub use arcana_codegen_impl::VersionedEvent; diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..321b857 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arcana-core" +version = "0.1.0" +edition = "2018" + +[dependencies] +ref-cast = "1.0" diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..d064704 --- /dev/null +++ b/core/README.md @@ -0,0 +1,2 @@ +arcana-core +=========== diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs new file mode 100644 index 0000000..6b2dae9 --- /dev/null +++ b/core/src/event/mod.rs @@ -0,0 +1,47 @@ +//! Event related definitions. + +/// [Event Sourcing] event that describes something that has occurred (happened +/// fact). +/// +/// A sequence of [`Event`]s may represent a concrete versioned state of an +/// Aggregate. +/// +/// [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html +pub trait Event { + /// Returns type of this [`Event`]. + /// + /// _Note:_ This should effectively be a constant value, and should never + /// change. + fn event_type(&self) -> &'static str; + + /// Returns version of this [`Event`]. + fn ver(&self) -> u16; +} + +/// Versioned [`Event`]. +/// +/// The single type of [`Event`] may have different versions, which allows +/// evolving [`Event`] in the type. To overcome the necessity of dealing with +/// multiple types of the same [`Event`], it's recommended for the last actual +/// version of [`Event`] to implement trait [`From`] its previous versions, so +/// they can be automatically transformed into the latest actual version of +pub trait Versioned { + /// Returns type of this [`Event`]. + /// + /// _Note:_ This should effectively be a constant value, and should never + /// change. + fn event_type() -> &'static str; + + /// Returns version of this [`Event`]. + fn ver() -> u16; +} + +impl Event for Ev { + fn event_type(&self) -> &'static str { + ::event_type() + } + + fn ver(&self) -> u16 { + ::ver() + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..12573b2 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,26 @@ +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] + +mod event; + +#[doc(inline)] +pub use event::{Event, Versioned as VersionedEvent}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bcca91a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] + +#[doc(inline)] +pub use arcana_core::{Event, VersionedEvent}; + +#[doc(inline)] +pub use arcana_codegen::VersionedEvent; From a6e18476369a9995989a1d999c3c35546a1ed985 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 13 Aug 2021 14:58:45 +0300 Subject: [PATCH 002/104] Add Makefile, GitHub Actions, PR and Issue templates --- .github/ISSUE_TEMPLATE/Bug.md | 56 +++++++++++ .github/ISSUE_TEMPLATE/Feature proposal.md | 48 +++++++++ .github/ISSUE_TEMPLATE/Question.md | 23 +++++ .github/ISSUE_TEMPLATE/Roadmap.md | 25 +++++ .github/ISSUE_TEMPLATE/Task.md | 38 ++++++++ .github/PULL_REQUEST_TEMPLATE.md | 70 +++++++++++++ .github/PULL_REQUEST_TEMPLATE/Bugfix.md | 55 +++++++++++ .github/PULL_REQUEST_TEMPLATE/Release.md | 41 ++++++++ .github/PULL_REQUEST_TEMPLATE/Roadmap.md | 57 +++++++++++ .github/workflows/ci.yml | 108 +++++++++++++++++++++ Makefile | 88 +++++++++++++++++ codegen/impl/src/lib.rs | 8 -- src/lib.rs | 2 +- 13 files changed, 610 insertions(+), 9 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug.md create mode 100644 .github/ISSUE_TEMPLATE/Feature proposal.md create mode 100644 .github/ISSUE_TEMPLATE/Question.md create mode 100644 .github/ISSUE_TEMPLATE/Roadmap.md create mode 100644 .github/ISSUE_TEMPLATE/Task.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/Bugfix.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/Release.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/Roadmap.md create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md new file mode 100644 index 0000000..66163c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug.md @@ -0,0 +1,56 @@ +--- +name: Bug +about: Report a bug related to the project. +labels: bug +--- + +Revealed from + +Caused by + +Related to + + + + + + +## Summary + + + + + + +## Steps to reproduce + + + + + + +## What is the current _bug_ behavior? + + + + + + +## What is the expected _correct_ behavior? + + + + + + +## Relevant logs and/or screenshots + + + + + + +## Possible fixes + + + diff --git a/.github/ISSUE_TEMPLATE/Feature proposal.md b/.github/ISSUE_TEMPLATE/Feature proposal.md new file mode 100644 index 0000000..fe6bb73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature proposal.md @@ -0,0 +1,48 @@ +--- +name: Feature proposal +about: Suggest a new feature for the project. +labels: feature +--- + +Revealed from + +Related to + + + + + + +## Problem to solve + + + + + + +## Proposal + + + + + + +## Prior art + + + + + + + +## Alternatives + + + + + + +## Links & references + + + diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..899173e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,23 @@ +--- +name: Question +about: Ask a question related to the project. +labels: question +--- + +Related to + + + + + + +## Background + + + + + + +## Question + + diff --git a/.github/ISSUE_TEMPLATE/Roadmap.md b/.github/ISSUE_TEMPLATE/Roadmap.md new file mode 100644 index 0000000..a1af203 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Roadmap.md @@ -0,0 +1,25 @@ +--- +name: Roadmap +about: Provide a roadmap for reaching a milestone or implementing a big feature. +labels: roadmap +--- + +Related to + + + + + + +## Summary + + + + + + +## Roadmap + + + +- [ ] 1. First step. () diff --git a/.github/ISSUE_TEMPLATE/Task.md b/.github/ISSUE_TEMPLATE/Task.md new file mode 100644 index 0000000..eb0810c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Task.md @@ -0,0 +1,38 @@ +--- +name: Task +about: Describe a task to be done. +--- + +Part of + +Revealed from + +Required for + +Requires + +Related to + + + + + + +## Background + + + + + + +## Problem to solve + + + + + + +## Possible solutions + + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..51761a5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,70 @@ +--- +name: Task +about: Perform a task related to the project. +--- + + + + + + + + + + +Resolves + +Part of + +Required for + +Requires + +Related to + + + + + + +## Synopsis + + + + + + +## Solution + + + + + + +## Checklist + +- Created PR: + - [ ] In [draft mode][l:1] + - [ ] Name contains `Draft: ` prefix + - [ ] Name contains issue reference + - [ ] Has `k::` labels applied + - [ ] Has assignee +- [ ] Documentation is updated (if required) +- [ ] Tests are updated (if required) +- [ ] Changes conform code style +- [ ] CHANGELOG entry is added (if required) +- [ ] FCM (final commit message) is posted + - [ ] and approved +- [ ] [Review][l:2] is completed and changes are approved +- Before merge: + - [ ] Milestone is set + - [ ] PR's name and description are correct and up-to-date + - [ ] `Draft: ` prefix is removed + - [ ] All temporary labels are removed + + + + + +[l:1]: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests +[l:2]: https://help.github.com/en/articles/reviewing-changes-in-pull-requests diff --git a/.github/PULL_REQUEST_TEMPLATE/Bugfix.md b/.github/PULL_REQUEST_TEMPLATE/Bugfix.md new file mode 100644 index 0000000..80d2a5e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/Bugfix.md @@ -0,0 +1,55 @@ +--- +name: Bugfix +about: Resolve a bug related to the project. +labels: enhancement +--- + + + + +Resolves + + + + +## Synopsis + + + + + + +## Solution + + + + + + +## Checklist + +- Created PR: + - [ ] In [draft mode][l:1] + - [ ] Name contains `Draft: ` prefix + - [ ] Name contains issue reference + - [ ] Has `k::` labels applied + - [ ] Has assignee +- [ ] Documentation is updated (if required) +- [ ] Tests are updated (if required) +- [ ] Changes conform code style +- [ ] CHANGELOG entry is added (if required) +- [ ] FCM (final commit message) is posted + - [ ] and approved +- [ ] [Review][l:2] is completed and changes are approved +- Before merge: + - [ ] Milestone is set + - [ ] PR's name and description are correct and up-to-date + - [ ] `Draft: ` prefix is removed + - [ ] All temporary labels are removed + + + + + +[l:1]: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests +[l:2]: https://help.github.com/en/articles/reviewing-changes-in-pull-requests diff --git a/.github/PULL_REQUEST_TEMPLATE/Release.md b/.github/PULL_REQUEST_TEMPLATE/Release.md new file mode 100644 index 0000000..d6a806d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/Release.md @@ -0,0 +1,41 @@ +--- +name: Release +about: Prepare a new release of the project. +labels: enhancement, k::documentation +--- + + + + +Prepares []() release. + + + + +## Checklist + +- Created PR: + - [ ] In [draft mode][l:1] + - [ ] Name contains `Draft: ` prefix + - [ ] Name contains issue reference + - [ ] Has `k::` labels applied + - [ ] Has assignee +- [ ] Documentation is updated (if required) +- [ ] Tests are updated (if required) +- [ ] Changes conform code style +- [ ] CHANGELOG entry is added (if required) +- [ ] FCM (final commit message) is posted + - [ ] and approved +- [ ] [Review][l:2] is completed and changes are approved +- Before merge: + - [ ] Milestone is set + - [ ] PR's name and description are correct and up-to-date + - [ ] `Draft: ` prefix is removed + - [ ] All temporary labels are removed + + + + + +[l:1]: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests +[l:2]: https://help.github.com/en/articles/reviewing-changes-in-pull-requests diff --git a/.github/PULL_REQUEST_TEMPLATE/Roadmap.md b/.github/PULL_REQUEST_TEMPLATE/Roadmap.md new file mode 100644 index 0000000..3b40eae --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/Roadmap.md @@ -0,0 +1,57 @@ +--- +name: Roadmap +about: Provide a roadmap for implementing a big feature. +labels: roadmap +--- + + + + +Resolves + +Required for + +Related to + + + + + + +## Summary + + + + + + +## Roadmap + + + +- [ ] 1. First step. () + + + + +## Checklist + +- Created PR: + - [ ] In [draft mode][l:1] + - [ ] Name contains `Draft: ` prefix + - [ ] Name contains issue reference + - [ ] Has `k::` labels applied + - [ ] Has assignee +- [ ] [Review][l:2] is completed and changes are approved +- Before merge: + - [ ] Milestone is set + - [ ] PR's name and description are correct and up-to-date + - [ ] `Draft: ` prefix is removed + - [ ] All temporary labels are removed + + + + + +[l:1]: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests +[l:2]: https://help.github.com/en/articles/reviewing-changes-in-pull-requests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f56b0c9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,108 @@ +name: CI + +on: [push, pull_request] + +env: + RUST_BACKTRACE: 1 + +jobs: + + ########################## + # Linting and formatting # + ########################## + + clippy: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + + - run: make cargo.lint + + rustfmt: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt + + - run: make cargo.fmt check=yes + + + + + ########### + # Testing # + ########### + + test: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + strategy: + fail-fast: false + matrix: + crate: + - cucumber_rust + - cucumber_rust_codegen + os: + - ubuntu + - macOS + - windows + toolchain: + - stable + - beta + - nightly + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + + - run: make test crate=${{ matrix.crate }} + + + + + ################# + # Documentation # + ################# + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - run: make cargo.doc open=no + + - name: Finalize + run: | + CRATE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]' | cut -f2 -d"/") + echo "" > target/doc/index.html + touch target/doc/.nojekyll + - name: Upload as artifact + uses: actions/upload-artifact@v2 + with: + name: Documentation + path: target/doc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7377d87 --- /dev/null +++ b/Makefile @@ -0,0 +1,88 @@ +############################### +# Common defaults/definitions # +############################### + +comma := , + +# Checks two given strings for equality. +eq = $(if $(or $(1),$(2)),$(and $(findstring $(1),$(2)),\ + $(findstring $(2),$(1))),1) + + + + +########### +# Aliases # +########### + +docs: cargo.doc + + +fmt: cargo.fmt + + +lint: cargo.lint + + + + +################## +# Cargo commands # +################## + +# Generate crates documentation from Rust sources. +# +# Usage: +# make cargo.doc [crate=] [open=(yes|no)] [clean=(no|yes)] + +cargo.doc: +ifeq ($(clean),yes) + @rm -rf target/doc/ +endif + cargo +stable doc $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + --all-features \ + $(if $(call eq,$(open),no),,--open) + + +# Format Rust sources with rustfmt. +# +# Usage: +# make cargo.fmt [check=(no|yes)] + +cargo.fmt: + cargo +nightly fmt --all $(if $(call eq,$(check),yes),-- --check,) + + +# Lint Rust sources with Clippy. +# +# Usage: +# make cargo.lint + +cargo.lint: + cargo +stable clippy --workspace -- -D clippy::pedantic -D warnings + + + + +#################### +# Testing commands # +#################### + +# Run Rust tests of project. +# +# Usage: +# make test [crate=] + +test: + cargo +stable test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + --all-features + + + + +################## +# .PHONY section # +################## + +.PHONY: docs fmt lint test \ + cargo.doc cargo.fmt cargo.lint diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 5ad4f02..9b4629f 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -25,14 +25,6 @@ mod event; use proc_macro::TokenStream; /// Macro for deriving `arcana::VersionedEvent`. -/// -/// # Example -/// -/// ``` -/// #[derive(VersionedEvent)] -/// #[type = "event", version = 1] -/// struct Event; -/// ``` #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { event::derive(input.into()) diff --git a/src/lib.rs b/src/lib.rs index bcca91a..762d693 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,5 +23,5 @@ #[doc(inline)] pub use arcana_core::{Event, VersionedEvent}; -#[doc(inline)] +/// Macro for deriving [`VersionedEvent`](trait@VersionedEvent). pub use arcana_codegen::VersionedEvent; From 238058db73459e75ec711644d23d698dcf9915b2 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 13 Aug 2021 15:14:23 +0300 Subject: [PATCH 003/104] Add EventInitialized, EventSourced and InitialEvent --- core/Cargo.toml | 1 + core/src/event/mod.rs | 33 +++++++++++++++++++++++++++++++++ core/src/lib.rs | 5 ++++- src/lib.rs | 2 +- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 321b857..862f968 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2018" [dependencies] +derive_more = "0.99" ref-cast = "1.0" diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index 6b2dae9..1c0d6de 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -1,5 +1,7 @@ //! Event related definitions. +use ref_cast::RefCast; + /// [Event Sourcing] event that describes something that has occurred (happened /// fact). /// @@ -45,3 +47,34 @@ impl Event for Ev { ::ver() } } + +/// State that can be calculated by applying specified [`Event`]. +pub trait Sourced { + /// Applies given [`Event`] to the current state. + fn apply(&mut self, event: &Ev); +} + +impl> Sourced for Option { + fn apply(&mut self, event: &Ev) { + if let Some(agg) = self { + agg.apply(event); + } + } +} + +/// Before items can be [`Sourced`], they need to be [`Initialized`]. +pub trait Initialized { + /// Creates initial state from given [`Event`]. + fn init(event: &Ev) -> Self; +} + +/// Wrapper-type intended for [`Event`]s that can initialize [`Sourced`] items. +#[derive(Debug, RefCast)] +#[repr(transparent)] +pub struct Initial(pub Ev); + +impl> Sourced> for Option { + fn apply(&mut self, event: &Initial) { + *self = Some(Agg::init(&event.0)); + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 12573b2..35a5d4d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,4 +23,7 @@ mod event; #[doc(inline)] -pub use event::{Event, Versioned as VersionedEvent}; +pub use event::{ + Event, Initial as InitialEvent, Initialized as EventInitialized, Sourced as EventSourced, + Versioned as VersionedEvent, +}; diff --git a/src/lib.rs b/src/lib.rs index 762d693..1a04267 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ )] #[doc(inline)] -pub use arcana_core::{Event, VersionedEvent}; +pub use arcana_core::{Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent}; /// Macro for deriving [`VersionedEvent`](trait@VersionedEvent). pub use arcana_codegen::VersionedEvent; From 83f99111dba6a960af8fca736861edc2aa3c74f1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 13 Aug 2021 15:27:10 +0300 Subject: [PATCH 004/104] Fix CI --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f56b0c9..04caf7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,8 +56,10 @@ jobs: fail-fast: false matrix: crate: - - cucumber_rust - - cucumber_rust_codegen + - arcana + - arcana-core + - arcana-codegen + - arcana-codegen-impl os: - ubuntu - macOS From f31ca0cbc36dcc6ee7d76bac7dcd689749425702 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 16 Aug 2021 15:17:35 +0300 Subject: [PATCH 005/104] Finally check uniqueness of event_type() and ver() at compile-time! --- Cargo.toml | 2 + codegen/impl/Cargo.toml | 5 +- codegen/impl/src/event/mod.rs | 187 ++++++++++++++++++++++------ codegen/impl/src/event/versioned.rs | 137 ++++++++++++++++++++ codegen/impl/src/lib.rs | 10 +- codegen/src/lib.rs | 2 +- examples/event.rs | 23 ++++ src/lib.rs | 5 +- src/private.rs | 90 +++++++++++++ 9 files changed, 416 insertions(+), 45 deletions(-) create mode 100644 codegen/impl/src/event/versioned.rs create mode 100644 examples/event.rs create mode 100644 src/private.rs diff --git a/Cargo.toml b/Cargo.toml index 8903e15..ac2f29c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2018" [dependencies] arcana-core = { version = "0.1.0", path = "./core" } arcana-codegen = { version = "0.1.0", path = "./codegen" } +static_assertions = "1.1" +once_cell = "1.8" [workspace] members = ["codegen", "codegen/impl", "core"] diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 73995bd..46889f9 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -7,9 +7,8 @@ edition = "2018" proc-macro = true [dependencies] +itertools = "0.10" proc-macro2 = "1.0" quote = "1.0" syn = "1.0" -[dependencies.synthez] - git = "https://github.com/arcana-rs/synthez" - branch = "fix-raw-idents" +synthez = "0.1" diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index daa6221..ca23d23 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -1,51 +1,142 @@ //! TODO +pub(crate) mod versioned; + +use std::convert::TryFrom; + use proc_macro2::TokenStream; use quote::quote; -use syn::Result; -use synthez::ParseAttrs; +use syn::{punctuated::Punctuated, spanned::Spanned as _, Result}; +use synthez::ToTokens; + +const MAX_EVENTS: usize = 10000; -/// Derives `serde::Deserialize` for `arcana::VersionedEvent`. +/// Derives `arcana::Event` for enum. pub(crate) fn derive(input: TokenStream) -> Result { let input: syn::DeriveInput = syn::parse2(input)?; - let attrs: Attrs = Attrs::parse_attrs("event", &input)?; - - match (attrs.r#type, attrs.version) { - (Some(event_type), Some(event_version)) => { - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - Ok(quote! { - #[automatically_derived] - impl #impl_generics ::arcana::VersionedEvent for - #name #ty_generics #where_clause - { - #[inline(always)] - fn event_type() -> &'static str { - #event_type + let definitions = EnumDefinitions::try_from(input)?; + + Ok(quote! { #definitions }) +} + +#[derive(ToTokens)] +#[to_tokens(append(impl_from, unique_event_type_and_ver))] +struct EnumDefinitions { + ident: syn::Ident, + generics: syn::Generics, + variants: Punctuated, +} + +impl EnumDefinitions { + fn impl_from(&self) -> TokenStream { + let name = &self.ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (event_types, event_vers): (TokenStream, TokenStream) = self + .variants + .iter() + .map(|variant| { + let name = &variant.ident; + + let generate_variant = |func: TokenStream| match &variant.fields { + syn::Fields::Named(named) => { + let field = &named.named.iter().next().unwrap().ident; + quote! { + Self::#name { #field } => { + ::arcana::Event::#func(#field) + } + } } + syn::Fields::Unnamed(_) => { + quote! { + Self::#name(inner) => { + ::arcana::Event::#func(inner) + } + } + } + syn::Fields::Unit => unreachable!(), + }; + + let (ty, ver) = ( + generate_variant(quote! { event_type }), + generate_variant(quote! { ver }), + ); + + (quote! { #ty }, quote! { #ver }) + }) + .unzip(); + + quote! { + #[automatically_derived] + impl #impl_generics ::arcana::Event for + #name #ty_generics #where_clause + { + #[inline(always)] + fn event_type(&self) -> &'static str { + match self { + #event_types + } + } - #[inline(always)] - fn ver() -> u16 { - #event_version + #[inline(always)] + fn ver(&self) -> u16 { + match self { + #event_vers } } + } + } + } + + fn unique_event_type_and_ver(&self) -> TokenStream { + let name = &self.ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let event_variants = self + .variants + .iter() + .map(|variant| { + let ty = &variant.fields.iter().next().unwrap().ty; + quote! { #ty, } }) + .collect::(); + let max = MAX_EVENTS; + + quote! { + impl #impl_generics #name #ty_generics #where_clause { + ::arcana::unique_event_type_and_ver_for_enum!( + #max, #event_variants + ); + } + + arcana::unique_event_type_and_ver_check!(#name); } - _ => Err(syn::Error::new_spanned( - input, - "`type` and `version` arguments expected", - )), } } -#[derive(Default, ParseAttrs)] -struct Attrs { - #[parse(value)] - r#type: Option, +impl TryFrom for EnumDefinitions { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> Result { + let data = if let syn::Data::Enum(data) = &input.data { + data + } else { + return Err(syn::Error::new(input.span(), "Expected enum")); + }; - #[parse(value)] - version: Option, + for variant in &data.variants { + if variant.fields.len() != 1 { + return Err(syn::Error::new( + variant.span(), + "Enum variants must have exactly 1 field", + )); + } + } + + Ok(Self { + ident: input.ident, + generics: input.generics, + variants: data.variants.clone(), + }) + } } #[cfg(test)] @@ -53,23 +144,41 @@ mod spec { use super::{derive, quote}; #[test] - fn derives_struct_impl() { + fn derives_enum_impl() { let input = syn::parse_quote! { - #[event(type = "event", version = 1)] - struct Event; + enum Event { + Event1(EventUnnamend), + Event2 { + event: EventNamed, + } + } }; let output = quote! { #[automatically_derived] - impl ::arcana::VersionedEvent for Event { + impl ::arcana::Event for Event { #[inline(always)] - fn event_type() -> &'static str { - "event" + fn event_type(&self) -> &'static str { + match self { + Self::Event1(inner) => { + ::arcana::Event::event_type(inner) + } + Self::Event2 { event } => { + ::arcana::Event::event_type(event) + } + } } #[inline(always)] - fn ver() -> u16 { - 1 + fn ver(&self) -> u16 { + match self { + Self::Event1(inner) => { + ::arcana::Event::ver(inner) + } + Self::Event2 { event } => { + ::arcana::Event::ver(event) + } + } } } }; diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs new file mode 100644 index 0000000..75e94e7 --- /dev/null +++ b/codegen/impl/src/event/versioned.rs @@ -0,0 +1,137 @@ +//! TODO + +use std::convert::TryFrom; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{spanned::Spanned as _, Result}; +use synthez::{ParseAttrs, ToTokens}; + +/// Derives `arcana::VersionedEvent` for struct. +pub(crate) fn derive(input: TokenStream) -> Result { + let input = syn::parse2::(input)?; + let definitions = Definitions::try_from(input)?; + + Ok(quote! { #definitions }) +} + +#[derive(ToTokens)] +#[to_tokens(append(impl_from, unique_event_type_and_ver))] +struct Definitions { + ident: syn::Ident, + generics: syn::Generics, + event_type: syn::LitStr, + event_ver: syn::LitInt, +} + +impl Definitions { + fn impl_from(&self) -> TokenStream { + let name = &self.ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (event_type, event_ver) = (&self.event_type, &self.event_ver); + + quote! { + #[automatically_derived] + impl #impl_generics ::arcana::VersionedEvent for + #name #ty_generics #where_clause + { + #[inline(always)] + fn event_type() -> &'static str { + #event_type + } + + #[inline(always)] + fn ver() -> u16 { + #event_ver + } + } + } + } + + fn unique_event_type_and_ver(&self) -> TokenStream { + let name = &self.ident; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (event_type, event_ver) = (&self.event_type, &self.event_ver); + let max = super::MAX_EVENTS; + + quote! { + impl #impl_generics #name #ty_generics #where_clause { + ::arcana::unique_event_type_and_ver_for_struct!( + #max, #event_type, #event_ver + ); + } + } + } +} + +impl TryFrom for Definitions { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> Result { + if !matches!(input.data, syn::Data::Struct(..)) { + return Err(syn::Error::new(input.span(), "Expected struct")); + } + + let attrs: Attrs = Attrs::parse_attrs("event", &input)?; + let (event_type, event_ver) = match (attrs.r#type, attrs.version) { + (Some(event_type), Some(event_ver)) => (event_type, event_ver), + _ => { + return Err(syn::Error::new_spanned( + input, + "`type` and `version` arguments expected", + )) + } + }; + + Ok(Self { + ident: input.ident, + generics: input.generics, + event_type, + event_ver, + }) + } +} + +#[derive(Default, ParseAttrs)] +struct Attrs { + #[parse(value)] + r#type: Option, + + #[parse(value)] + version: Option, +} + +#[cfg(test)] +mod spec { + use super::{derive, quote}; + + #[test] + fn derives_struct_impl() { + let input = syn::parse_quote! { + #[event(type = "event", version = 1)] + struct Event; + }; + + let output = quote! { + impl Event { + pub const EVENT_TYPE: &'static str = "event"; + pub const EVENT_VER: u16 = 1; + } + + #[automatically_derived] + impl ::arcana::VersionedEvent for Event { + #[inline(always)] + fn event_type() -> &'static str { + Self::EVENT_TYPE + } + + #[inline(always)] + fn ver() -> u16 { + Self::EVENT_VER + } + } + }; + + assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + } +} diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 9b4629f..677ca1b 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -24,10 +24,18 @@ mod event; use proc_macro::TokenStream; +/// Macro for deriving `arcana::Event`. +#[proc_macro_derive(Event, attributes(event))] +pub fn derive_event(input: TokenStream) -> TokenStream { + event::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + /// Macro for deriving `arcana::VersionedEvent`. #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { - event::derive(input.into()) + event::versioned::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 2de4abf..21dc255 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -21,4 +21,4 @@ )] #[doc(inline)] -pub use arcana_codegen_impl::VersionedEvent; +pub use arcana_codegen_impl::{Event, VersionedEvent}; diff --git a/examples/event.rs b/examples/event.rs new file mode 100644 index 0000000..371b310 --- /dev/null +++ b/examples/event.rs @@ -0,0 +1,23 @@ +use arcana::{Event, VersionedEvent}; + +#[derive(VersionedEvent)] +#[event(type = "chat", version = 1)] +struct ChatEvent; + +#[derive(VersionedEvent)] +#[event(type = "file", version = 1)] +struct FileEvent; + +#[derive(Event)] +enum AnyEvent { + Chat(ChatEvent), + File { event: FileEvent }, +} + +fn main() { + let ev = AnyEvent::Chat(ChatEvent); + assert_eq!(ev.event_type(), "chat"); + + let ev = AnyEvent::File { event: FileEvent }; + assert_eq!(ev.event_type(), "file"); +} diff --git a/src/lib.rs b/src/lib.rs index 1a04267..dad4c03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,11 @@ unused_results )] +#[doc(hidden)] +pub mod private; + #[doc(inline)] pub use arcana_core::{Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent}; /// Macro for deriving [`VersionedEvent`](trait@VersionedEvent). -pub use arcana_codegen::VersionedEvent; +pub use arcana_codegen::{Event, VersionedEvent}; diff --git a/src/private.rs b/src/private.rs new file mode 100644 index 0000000..1252e2f --- /dev/null +++ b/src/private.rs @@ -0,0 +1,90 @@ +pub use static_assertions as sa; + +pub mod unique_event_type_and_ver { + + #[doc(hidden)] + #[macro_export] + macro_rules! unique_event_type_and_ver_for_struct { + ($max_events: literal, $event_type: literal, $event_ver: literal) => { + pub const fn __arcana_event_types() -> [Option<(&'static str, u16)>; $max_events] { + let mut res = [None; $max_events]; + res[0] = Some(($event_type, $event_ver)); + res + } + }; + } + + #[doc(hidden)] + #[macro_export] + macro_rules! unique_event_type_and_ver_for_enum { + ($max_events: literal, $($event_type: ty),* $(,)?) => { + pub const fn __arcana_event_types() -> + [Option<(&'static str, u16)>; $max_events] + { + let mut res = [None; $max_events]; + + let mut global = 0; + + $({ + let ev = <$event_type>::__arcana_event_types(); + let mut local = 0; + while let Some(s) = ev[local] { + res[global] = Some(s); + local += 1; + global += 1; + } + })* + + res + } + }; + } + + #[doc(hidden)] + #[macro_export] + macro_rules! unique_event_type_and_ver_check { + ($event: ty) => { + $crate::private::sa::const_assert!( + $crate::private::unique_event_type_and_ver::all_unique( + <$event>::__arcana_event_types() + ) + ); + }; + } + + #[doc(hidden)] + #[must_use] + pub const fn all_unique(types: [Option<(&str, u16)>; N]) -> bool { + const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } + + let mut outer = 0; + while let Some((outer_type, outer_ver)) = types[outer] { + let mut inner = outer + 1; + while let Some((inner_type, inner_ver)) = types[inner] { + if str_eq(inner_type, outer_type) && inner_ver == outer_ver { + return false; + } + inner += 1; + } + outer += 1; + } + + true + } +} From 42c5b4d5bf032c3bde581a058a2b61924e5b28ed Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 16 Aug 2021 16:02:39 +0300 Subject: [PATCH 006/104] Add docs explaining unique checks macro shenanigans [skip ci] --- codegen/impl/src/event/mod.rs | 4 +-- codegen/impl/src/event/versioned.rs | 2 +- src/lib.rs | 40 +++++++++++++++++++++++++++-- src/private.rs | 35 ++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index ca23d23..ebc25a7 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -9,7 +9,7 @@ use quote::quote; use syn::{punctuated::Punctuated, spanned::Spanned as _, Result}; use synthez::ToTokens; -const MAX_EVENTS: usize = 10000; +const MAX_UNIQUE_EVENTS: usize = 100000; /// Derives `arcana::Event` for enum. pub(crate) fn derive(input: TokenStream) -> Result { @@ -98,7 +98,7 @@ impl EnumDefinitions { quote! { #ty, } }) .collect::(); - let max = MAX_EVENTS; + let max = MAX_UNIQUE_EVENTS; quote! { impl #impl_generics #name #ty_generics #where_clause { diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index 75e94e7..40a87e2 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -52,7 +52,7 @@ impl Definitions { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let (event_type, event_ver) = (&self.event_type, &self.event_ver); - let max = super::MAX_EVENTS; + let max = super::MAX_UNIQUE_EVENTS; quote! { impl #impl_generics #name #ty_generics #where_clause { diff --git a/src/lib.rs b/src/lib.rs index dad4c03..52a52cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,5 +26,41 @@ pub mod private; #[doc(inline)] pub use arcana_core::{Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent}; -/// Macro for deriving [`VersionedEvent`](trait@VersionedEvent). -pub use arcana_codegen::{Event, VersionedEvent}; +/// Macro for deriving [`Event`](trait@Event) on enums. For structs consider +/// [`VersionedEvent`](macro@VersionedEvent). +/// +/// This macro ensures that every combination of `event_type` and `ver` are +/// unique. The only limitation is that every underlying +/// [`Event`](trait@Event) or [`VersionedEvent`](trait@VersionedEvent) impls are +/// generated with proc macros. +/// +/// # Example +/// +/// ```compile_fail +/// # use arcana::{Event, VersionedEvent}; +/// +/// #[derive(VersionedEvent)] +/// #[event(type = "chat", version = 1)] +/// struct ChatEvent; +/// +/// #[derive(VersionedEvent)] +/// #[event(type = "file", version = 1)] +/// struct FileEvent; +/// +/// #[derive(Event)] +/// enum AnyEvent { +/// Chat(ChatEvent), +/// File { event: FileEvent }, +/// } +/// +/// #[derive(Event)] +/// enum DuplicatedEvent { +/// Any(AnyEvent), +/// File(event: FileEvent), +/// } +/// ``` +pub use arcana_codegen::Event; + +/// Macro for deriving [`VersionedEvent`](trait@VersionedEvent) on structs. For +/// enums, consisting of different events consider [`Event`](macro@Event). +pub use arcana_codegen::VersionedEvent; diff --git a/src/private.rs b/src/private.rs index 1252e2f..a000fa6 100644 --- a/src/private.rs +++ b/src/private.rs @@ -1,7 +1,40 @@ +//! Module for utils used in proc macro expansion. + pub use static_assertions as sa; +/// Utils for ensuring that every [`Event`] variant has a unique combination of +/// `event_type` and `ver`. +/// +/// # Explanation +/// +/// Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates +/// `const fn __arcana_event_types() -> [Option<(&'static str, u16)>; size]` +/// const function. Size of outputted array determines max count of unique +/// [`VersionedEvent`]s inside [`Event`] and is tweakable inside +/// `arcana_codegen_impl` crate (default is `100_000` which should be plenty). +/// +/// - Structs +/// +/// [`unique_event_type_and_ver_for_struct`] macro generates function, which +/// returns array with only first occupied entry. The rest of them are +/// [`None`]. +/// +/// - Enums +/// +/// [`unique_event_type_and_ver_for_enum`] macro generates function, which +/// glues subtypes arrays into single continues array. First `n` entries are +/// occupied, while the rest of them are [`None`], where `n` is the number of +/// [`VersionedEvent`]s. As structs deriving [`VersionedEvent`] and enums +/// deriving [`Event`] have the same output by `__arcana_event_types()` const +/// function, top-level enum variants can have different levels of nesting. +/// +/// [`unique_event_type_and_ver_check`] macro generates [`const_assert`] +/// check, which fails in case of duplicated `event_type` and `ver`. +/// +/// +/// [`Event`]: trait@crate::Event +/// [`VersionedEvent`]: trait@crate::VersionedEvent pub mod unique_event_type_and_ver { - #[doc(hidden)] #[macro_export] macro_rules! unique_event_type_and_ver_for_struct { From 7dfe985360beb8be3b006055d918985bf5ea13e4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 09:44:28 +0300 Subject: [PATCH 007/104] Add ability to skip uniqueness check for event_type and ver for Event proc macro --- .rustfmt.toml | 16 ++ codegen/impl/src/event/mod.rs | 254 +++++++++++++++++++++++++--- codegen/impl/src/event/versioned.rs | 77 +++++++-- codegen/impl/src/lib.rs | 17 ++ core/src/event/mod.rs | 4 +- core/src/lib.rs | 4 +- src/lib.rs | 66 +++++++- src/private.rs | 12 +- 8 files changed, 401 insertions(+), 49 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..92d68fc --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,16 @@ +# Project configuration for rustfmt Rust code formatter. +# See full list of configurations at: +# https://github.com/rust-lang-nursery/rustfmt/blob/master/Configurations.md + +max_width = 80 +format_strings = false +imports_granularity = "Crate" + +format_code_in_doc_comments = true +format_macro_matchers = true +use_try_shorthand = true + +error_on_line_overflow = true +error_on_unformatted = true + +unstable_features = true diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index ebc25a7..a5ab44a 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -1,44 +1,53 @@ -//! TODO +//! Definition of `arcana::Event` derive macro for enums. pub(crate) mod versioned; -use std::convert::TryFrom; +use std::{convert::TryFrom, result::Result as StdResult, str::FromStr}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{punctuated::Punctuated, spanned::Spanned as _, Result}; -use synthez::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Result, +}; +use synthez::{ParseAttrs, ToTokens}; -const MAX_UNIQUE_EVENTS: usize = 100000; +const MAX_UNIQUE_EVENTS: usize = 100_000; /// Derives `arcana::Event` for enum. pub(crate) fn derive(input: TokenStream) -> Result { let input: syn::DeriveInput = syn::parse2(input)?; - let definitions = EnumDefinitions::try_from(input)?; + let definitions = Definitions::try_from(input)?; Ok(quote! { #definitions }) } #[derive(ToTokens)] #[to_tokens(append(impl_from, unique_event_type_and_ver))] -struct EnumDefinitions { +struct Definitions { ident: syn::Ident, generics: syn::Generics, - variants: Punctuated, + variants: Vec<(syn::Variant, Attrs)>, + attrs: Attrs, } -impl EnumDefinitions { +impl Definitions { fn impl_from(&self) -> TokenStream { let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let (event_types, event_vers): (TokenStream, TokenStream) = self + let (impl_generics, ty_generics, where_clause) = + self.generics.split_for_impl(); + let (event_types, event_versions): (TokenStream, TokenStream) = self .variants .iter() - .map(|variant| { + .map(|(variant, _)| { let name = &variant.ident; - let generate_variant = |func: TokenStream| match &variant.fields { + let generate_variant = |func: TokenStream| match &variant.fields + { syn::Fields::Named(named) => { + // Unwrapping is safe here as we checked for + // `.len() == 1` in TryFrom impl. let field = &named.named.iter().next().unwrap().ident; quote! { Self::#name { #field } => { @@ -56,12 +65,10 @@ impl EnumDefinitions { syn::Fields::Unit => unreachable!(), }; - let (ty, ver) = ( + ( generate_variant(quote! { event_type }), generate_variant(quote! { ver }), - ); - - (quote! { #ty }, quote! { #ver }) + ) }) .unzip(); @@ -80,7 +87,7 @@ impl EnumDefinitions { #[inline(always)] fn ver(&self) -> u16 { match self { - #event_vers + #event_versions } } } @@ -88,17 +95,24 @@ impl EnumDefinitions { } fn unique_event_type_and_ver(&self) -> TokenStream { + if self.attrs.skip_check_unique_type_and_ver() { + return TokenStream::new(); + } + + let max = MAX_UNIQUE_EVENTS; let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + self.generics.split_for_impl(); let event_variants = self .variants .iter() - .map(|variant| { - let ty = &variant.fields.iter().next().unwrap().ty; - quote! { #ty, } + .filter_map(|(variant, attr)| { + (!attr.skip_check_unique_type_and_ver()).then(|| { + let ty = &variant.fields.iter().next().unwrap().ty; + quote! { #ty, } + }) }) .collect::(); - let max = MAX_UNIQUE_EVENTS; quote! { impl #impl_generics #name #ty_generics #where_clause { @@ -112,14 +126,18 @@ impl EnumDefinitions { } } -impl TryFrom for EnumDefinitions { +impl TryFrom for Definitions { type Error = syn::Error; fn try_from(input: syn::DeriveInput) -> Result { let data = if let syn::Data::Enum(data) = &input.data { data } else { - return Err(syn::Error::new(input.span(), "Expected enum")); + return Err(syn::Error::new( + input.span(), + "Expected enum. \ + Consider using arcana::VersionedEvent for structs", + )); }; for variant in &data.variants { @@ -131,14 +149,78 @@ impl TryFrom for EnumDefinitions { } } + let attrs = Attrs::parse_attrs("event", &input)?; + let variants = data + .variants + .iter() + .map(|variant| { + Ok((variant.clone(), Attrs::parse_attrs("event", variant)?)) + }) + .collect::>()?; + Ok(Self { ident: input.ident, generics: input.generics, - variants: data.variants.clone(), + variants, + attrs, + }) + } +} + +#[derive(Default, ParseAttrs)] +struct Attrs { + #[parse(value)] + skip: Option>, +} + +impl Attrs { + fn skip_check_unique_type_and_ver(&self) -> bool { + matches!( + self.skip.as_ref().map(|sp| sp.item), + Some(SkipAttr::CheckUniqueTypeAndVer), + ) + } +} + +#[derive(Clone, Debug)] +struct Spanning { + item: T, + span: Span, +} + +impl Spanned for Spanning { + fn span(&self) -> Span { + self.span + } +} + +#[derive(Clone, Copy, Debug)] +enum SkipAttr { + CheckUniqueTypeAndVer, +} + +impl Parse for Spanning { + fn parse(input: ParseStream<'_>) -> Result { + let ident = syn::Ident::parse(input)?; + Ok(Spanning { + item: SkipAttr::from_str(&ident.to_string()) + .map_err(|err| syn::Error::new(ident.span(), err))?, + span: ident.span(), }) } } +impl FromStr for SkipAttr { + type Err = &'static str; + + fn from_str(s: &str) -> StdResult { + match s { + "check_unique_type_and_ver" => Ok(Self::CheckUniqueTypeAndVer), + _ => Err("unknown value"), + } + } +} + #[cfg(test)] mod spec { use super::{derive, quote}; @@ -181,8 +263,126 @@ mod spec { } } } + + impl Event { + ::arcana::unique_event_type_and_ver_for_enum!( + 100000usize, EventUnnamend, EventNamed, + ); + } + + arcana::unique_event_type_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); } + + #[test] + fn skip_unique_check_on_container() { + let input = syn::parse_quote! { + #[event(skip(check_unique_type_and_ver))] + enum Event { + Event1(EventUnnamend), + Event2 { + event: EventNamed, + } + } + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::Event for Event { + #[inline(always)] + fn event_type(&self) -> &'static str { + match self { + Self::Event1(inner) => { + ::arcana::Event::event_type(inner) + } + Self::Event2 { event } => { + ::arcana::Event::event_type(event) + } + } + } + + #[inline(always)] + fn ver(&self) -> u16 { + match self { + Self::Event1(inner) => { + ::arcana::Event::ver(inner) + } + Self::Event2 { event } => { + ::arcana::Event::ver(event) + } + } + } + } + }; + + assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + } + + #[test] + fn skip_unique_check_on_variant() { + let input = syn::parse_quote! { + enum Event { + #[event(skip(check_unique_type_and_ver))] + Event1(EventUnnamend), + Event2 { + event: EventNamed, + } + } + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::Event for Event { + #[inline(always)] + fn event_type(&self) -> &'static str { + match self { + Self::Event1(inner) => { + ::arcana::Event::event_type(inner) + } + Self::Event2 { event } => { + ::arcana::Event::event_type(event) + } + } + } + + #[inline(always)] + fn ver(&self) -> u16 { + match self { + Self::Event1(inner) => { + ::arcana::Event::ver(inner) + } + Self::Event2 { event } => { + ::arcana::Event::ver(event) + } + } + } + } + + impl Event { + ::arcana::unique_event_type_and_ver_for_enum!( + 100000usize, EventNamed, + ); + } + + arcana::unique_event_type_and_ver_check!(Event); + }; + + assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + } + + #[test] + fn errors_on_struct() { + let input = syn::parse_quote! { + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "Expected enum. Consider using arcana::VersionedEvent for structs", + ); + } } diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index 40a87e2..1659d59 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -1,4 +1,4 @@ -//! TODO +//! Definition of `arcana::VersionedEvent` derive macro for structs. use std::convert::TryFrom; @@ -27,7 +27,8 @@ struct Definitions { impl Definitions { fn impl_from(&self) -> TokenStream { let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + self.generics.split_for_impl(); let (event_type, event_ver) = (&self.event_type, &self.event_ver); quote! { @@ -50,7 +51,8 @@ impl Definitions { fn unique_event_type_and_ver(&self) -> TokenStream { let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + self.generics.split_for_impl(); let (event_type, event_ver) = (&self.event_type, &self.event_ver); let max = super::MAX_UNIQUE_EVENTS; @@ -69,10 +71,13 @@ impl TryFrom for Definitions { fn try_from(input: syn::DeriveInput) -> Result { if !matches!(input.data, syn::Data::Struct(..)) { - return Err(syn::Error::new(input.span(), "Expected struct")); + return Err(syn::Error::new( + input.span(), + "Expected struct. Consider using arcana::Event for enums", + )); } - let attrs: Attrs = Attrs::parse_attrs("event", &input)?; + let attrs = Attrs::parse_attrs("event", &input)?; let (event_type, event_ver) = match (attrs.r#type, attrs.version) { (Some(event_type), Some(event_ver)) => (event_type, event_ver), _ => { @@ -113,25 +118,73 @@ mod spec { }; let output = quote! { - impl Event { - pub const EVENT_TYPE: &'static str = "event"; - pub const EVENT_VER: u16 = 1; - } - #[automatically_derived] impl ::arcana::VersionedEvent for Event { #[inline(always)] fn event_type() -> &'static str { - Self::EVENT_TYPE + "event" } #[inline(always)] fn ver() -> u16 { - Self::EVENT_VER + 1 } } + + impl Event { + ::arcana::unique_event_type_and_ver_for_struct!( + 100000usize, "event", 1 + ); + } }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); } + + #[test] + fn type_argument_is_expected() { + let input = syn::parse_quote! { + #[event(version = 1)] + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "`type` and `version` arguments expected", + ); + } + + #[test] + fn version_argument_is_expected() { + let input = syn::parse_quote! { + #[event(type = "event")] + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "`type` and `version` arguments expected", + ); + } + + #[test] + fn errors_on_enum() { + let input = syn::parse_quote! { + #[event(type = "event", version = 1)] + enum Event { + Event1(Event1), + } + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "Expected struct. Consider using arcana::Event for enums", + ); + } } diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 677ca1b..69b68cc 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -25,6 +25,13 @@ mod event; use proc_macro::TokenStream; /// Macro for deriving `arcana::Event`. +/// +/// # Attribute arguments +/// +/// - `#[event(skip(unique_event_type_and_ver))]` — optional +/// +/// Use this value on whole container or particular enum variant to skip check +/// for unique combination of `event_type` and `ver`. #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { event::derive(input.into()) @@ -33,6 +40,16 @@ pub fn derive_event(input: TokenStream) -> TokenStream { } /// Macro for deriving `arcana::VersionedEvent`. +/// +/// # Attribute arguments +/// +/// - `#[event(type = "...")]` — required +/// +/// Value used in `fn event_type()` impl. +/// +/// - `#[event(ver = u16)]` — required +/// +/// Value used in `fn ver()` impl. #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { event::versioned::derive(input.into()) diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index 1c0d6de..d7eabb4 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -73,7 +73,9 @@ pub trait Initialized { #[repr(transparent)] pub struct Initial(pub Ev); -impl> Sourced> for Option { +impl> Sourced> + for Option +{ fn apply(&mut self, event: &Initial) { *self = Some(Agg::init(&event.0)); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 35a5d4d..ff223b9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,6 +24,6 @@ mod event; #[doc(inline)] pub use event::{ - Event, Initial as InitialEvent, Initialized as EventInitialized, Sourced as EventSourced, - Versioned as VersionedEvent, + Event, Initial as InitialEvent, Initialized as EventInitialized, + Sourced as EventSourced, Versioned as VersionedEvent, }; diff --git a/src/lib.rs b/src/lib.rs index 52a52cc..cf2b3da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,17 +24,26 @@ pub mod private; #[doc(inline)] -pub use arcana_core::{Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent}; +pub use arcana_core::{ + Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent, +}; /// Macro for deriving [`Event`](trait@Event) on enums. For structs consider /// [`VersionedEvent`](macro@VersionedEvent). /// /// This macro ensures that every combination of `event_type` and `ver` are /// unique. The only limitation is that every underlying -/// [`Event`](trait@Event) or [`VersionedEvent`](trait@VersionedEvent) impls are -/// generated with proc macros. +/// [`Event`](trait@Event) or [`VersionedEvent`](trait@VersionedEvent) impls +/// should be generated with proc macros. /// -/// # Example +/// # Attribute arguments +/// +/// - `#[event(skip(unique_event_type_and_ver))]` — optional +/// +/// Use this value on whole container or particular enum variant to skip check +/// for unique combination of `event_type` and `ver`. +/// +/// # Examples /// /// ```compile_fail /// # use arcana::{Event, VersionedEvent}; @@ -56,11 +65,58 @@ pub use arcana_core::{Event, EventInitialized, EventSourced, InitialEvent, Versi /// #[derive(Event)] /// enum DuplicatedEvent { /// Any(AnyEvent), -/// File(event: FileEvent), +/// File { +/// event: FileEvent, +/// }, +/// } +/// ``` +/// +/// ``` +/// # use arcana::{Event, VersionedEvent}; +/// +/// # #[derive(VersionedEvent)] +/// # #[event(type = "chat", version = 1)] +/// # struct ChatEvent; +/// +/// # #[derive(VersionedEvent)] +/// # #[event(type = "file", version = 1)] +/// # struct FileEvent; +/// +/// # #[derive(Event)] +/// # enum AnyEvent { +/// # Chat(ChatEvent), +/// # File { event: FileEvent }, +/// # } +/// +/// #[derive(Event)] +/// enum DuplicatedEvent { +/// Any(AnyEvent), +/// #[event(skip(unique_event_type_and_ver))] +/// File { +/// event: FileEvent, +/// }, /// } /// ``` pub use arcana_codegen::Event; /// Macro for deriving [`VersionedEvent`](trait@VersionedEvent) on structs. For /// enums, consisting of different events consider [`Event`](macro@Event). +/// +/// # Attribute arguments +/// +/// - `#[event(type = "...")]` — required +/// +/// Value used in `fn event_type()` impl. +/// +/// - `#[event(ver = u16)]` — required +/// +/// Value used in `fn ver()` impl. +/// +/// # Examples +/// +/// ``` +/// #[derive(VersionedEvent)] +/// #[event(type = "event", version = 1)] +/// struct Event; +/// ``` pub use arcana_codegen::VersionedEvent; diff --git a/src/private.rs b/src/private.rs index a000fa6..3ff0723 100644 --- a/src/private.rs +++ b/src/private.rs @@ -12,6 +12,8 @@ pub use static_assertions as sa; /// const function. Size of outputted array determines max count of unique /// [`VersionedEvent`]s inside [`Event`] and is tweakable inside /// `arcana_codegen_impl` crate (default is `100_000` which should be plenty). +/// As these arrays are used only at compile-time, there should be no +/// performance impact at runtime. /// /// - Structs /// @@ -32,6 +34,7 @@ pub use static_assertions as sa; /// check, which fails in case of duplicated `event_type` and `ver`. /// /// +/// [`const_assert`]: static_assertions::const_assert /// [`Event`]: trait@crate::Event /// [`VersionedEvent`]: trait@crate::VersionedEvent pub mod unique_event_type_and_ver { @@ -39,7 +42,9 @@ pub mod unique_event_type_and_ver { #[macro_export] macro_rules! unique_event_type_and_ver_for_struct { ($max_events: literal, $event_type: literal, $event_ver: literal) => { - pub const fn __arcana_event_types() -> [Option<(&'static str, u16)>; $max_events] { + #[allow(clippy::large_stack_arrays)] + pub const fn __arcana_event_types( + ) -> [Option<(&'static str, u16)>; $max_events] { let mut res = [None; $max_events]; res[0] = Some(($event_type, $event_ver)); res @@ -51,6 +56,7 @@ pub mod unique_event_type_and_ver { #[macro_export] macro_rules! unique_event_type_and_ver_for_enum { ($max_events: literal, $($event_type: ty),* $(,)?) => { + #[allow(clippy::large_stack_arrays)] pub const fn __arcana_event_types() -> [Option<(&'static str, u16)>; $max_events] { @@ -87,7 +93,9 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[must_use] - pub const fn all_unique(types: [Option<(&str, u16)>; N]) -> bool { + pub const fn all_unique( + types: [Option<(&str, u16)>; N], + ) -> bool { const fn str_eq(l: &str, r: &str) -> bool { let (l, r) = (l.as_bytes(), r.as_bytes()); From 7df18efc2a7dcee69a230cf5844f83f1d239253c Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 10:12:20 +0300 Subject: [PATCH 008/104] Add more tests --- codegen/impl/Cargo.toml | 1 - codegen/impl/src/event/mod.rs | 47 ++++++++++++++++++++++++++--- codegen/impl/src/event/versioned.rs | 4 ++- src/lib.rs | 18 +++++------ src/private.rs | 4 +-- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 46889f9..0955b5d 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" proc-macro = true [dependencies] -itertools = "0.10" proc-macro2 = "1.0" quote = "1.0" syn = "1.0" diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index a5ab44a..9a8d74e 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -121,7 +121,7 @@ impl Definitions { ); } - arcana::unique_event_type_and_ver_check!(#name); + ::arcana::unique_event_type_and_ver_check!(#name); } } } @@ -216,7 +216,9 @@ impl FromStr for SkipAttr { fn from_str(s: &str) -> StdResult { match s { "check_unique_type_and_ver" => Ok(Self::CheckUniqueTypeAndVer), - _ => Err("unknown value"), + _ => { + Err("Unknown value. Allowed values: check_unique_type_and_ver") + } } } } @@ -270,7 +272,7 @@ mod spec { ); } - arcana::unique_event_type_and_ver_check!(Event); + ::arcana::unique_event_type_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); @@ -366,12 +368,49 @@ mod spec { ); } - arcana::unique_event_type_and_ver_check!(Event); + ::arcana::unique_event_type_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); } + #[test] + fn errors_on_multiple_fields_in_variant() { + let input = syn::parse_quote! { + enum Event { + Event1(Event1), + Event2 { + event: Event2, + second_field: Event3, + } + } + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "Enum variants must have exactly 1 field", + ); + } + + #[test] + fn errors_on_unknown_attribute_value() { + let input = syn::parse_quote! { + enum Event { + #[event(skip(unknown))] + Event1(Event1), + } + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "Unknown value. Allowed values: check_unique_type_and_ver", + ); + } + #[test] fn errors_on_struct() { let input = syn::parse_quote! { diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index 1659d59..48eacb7 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -7,6 +7,8 @@ use quote::quote; use syn::{spanned::Spanned as _, Result}; use synthez::{ParseAttrs, ToTokens}; +use super::MAX_UNIQUE_EVENTS; + /// Derives `arcana::VersionedEvent` for struct. pub(crate) fn derive(input: TokenStream) -> Result { let input = syn::parse2::(input)?; @@ -54,7 +56,7 @@ impl Definitions { let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let (event_type, event_ver) = (&self.event_type, &self.event_ver); - let max = super::MAX_UNIQUE_EVENTS; + let max = MAX_UNIQUE_EVENTS; quote! { impl #impl_generics #name #ty_generics #where_clause { diff --git a/src/lib.rs b/src/lib.rs index cf2b3da..0107783 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ pub use arcana_core::{ /// /// ```compile_fail /// # use arcana::{Event, VersionedEvent}; -/// +/// # /// #[derive(VersionedEvent)] /// #[event(type = "chat", version = 1)] /// struct ChatEvent; @@ -65,33 +65,31 @@ pub use arcana_core::{ /// #[derive(Event)] /// enum DuplicatedEvent { /// Any(AnyEvent), -/// File { -/// event: FileEvent, -/// }, +/// File { event: FileEvent }, /// } /// ``` /// /// ``` /// # use arcana::{Event, VersionedEvent}; -/// +/// # /// # #[derive(VersionedEvent)] /// # #[event(type = "chat", version = 1)] /// # struct ChatEvent; -/// +/// # /// # #[derive(VersionedEvent)] /// # #[event(type = "file", version = 1)] /// # struct FileEvent; -/// +/// # /// # #[derive(Event)] /// # enum AnyEvent { /// # Chat(ChatEvent), /// # File { event: FileEvent }, /// # } -/// +/// # /// #[derive(Event)] /// enum DuplicatedEvent { /// Any(AnyEvent), -/// #[event(skip(unique_event_type_and_ver))] +/// #[event(skip(check_unique_type_and_ver))] /// File { /// event: FileEvent, /// }, @@ -115,6 +113,8 @@ pub use arcana_codegen::Event; /// # Examples /// /// ``` +/// # use arcana::VersionedEvent; +/// # /// #[derive(VersionedEvent)] /// #[event(type = "event", version = 1)] /// struct Event; diff --git a/src/private.rs b/src/private.rs index 3ff0723..42b7b31 100644 --- a/src/private.rs +++ b/src/private.rs @@ -41,7 +41,7 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[macro_export] macro_rules! unique_event_type_and_ver_for_struct { - ($max_events: literal, $event_type: literal, $event_ver: literal) => { + ($max_events:literal, $event_type:literal, $event_ver:literal) => { #[allow(clippy::large_stack_arrays)] pub const fn __arcana_event_types( ) -> [Option<(&'static str, u16)>; $max_events] { @@ -82,7 +82,7 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[macro_export] macro_rules! unique_event_type_and_ver_check { - ($event: ty) => { + ($event:ty) => { $crate::private::sa::const_assert!( $crate::private::unique_event_type_and_ver::all_unique( <$event>::__arcana_event_types() From d454eaf01e158ff033067d6cebf42559cdc99764 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 11:00:47 +0300 Subject: [PATCH 009/104] Final touches --- Cargo.toml | 4 ++++ codegen/impl/Cargo.toml | 1 + codegen/impl/src/event/mod.rs | 41 +++++++++++++++-------------------- codegen/impl/src/lib.rs | 17 --------------- src/lib.rs | 8 +++---- 5 files changed, 27 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac2f29c..3eec2ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "arcana" version = "0.1.0" edition = "2018" +[features] +default = ["derive"] +derive = [] + [dependencies] arcana-core = { version = "0.1.0", path = "./core" } arcana-codegen = { version = "0.1.0", path = "./codegen" } diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 0955b5d..d133a15 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -9,5 +9,6 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" +strum = { version = "0.21", features = ["derive"] } syn = "1.0" synthez = "0.1" diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 9a8d74e..3dd2430 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -2,21 +2,21 @@ pub(crate) mod versioned; -use std::{convert::TryFrom, result::Result as StdResult, str::FromStr}; +use std::{convert::TryFrom, str::FromStr as _}; use proc_macro2::{Span, TokenStream}; use quote::quote; +use strum::{EnumString, EnumVariantNames, VariantNames as _}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, - Result, }; use synthez::{ParseAttrs, ToTokens}; const MAX_UNIQUE_EVENTS: usize = 100_000; /// Derives `arcana::Event` for enum. -pub(crate) fn derive(input: TokenStream) -> Result { +pub(crate) fn derive(input: TokenStream) -> syn::Result { let input: syn::DeriveInput = syn::parse2(input)?; let definitions = Definitions::try_from(input)?; @@ -129,14 +129,14 @@ impl Definitions { impl TryFrom for Definitions { type Error = syn::Error; - fn try_from(input: syn::DeriveInput) -> Result { + fn try_from(input: syn::DeriveInput) -> syn::Result { let data = if let syn::Data::Enum(data) = &input.data { data } else { return Err(syn::Error::new( input.span(), "Expected enum. \ - Consider using arcana::VersionedEvent for structs", + Consider using arcana::VersionedEvent for structs", )); }; @@ -156,7 +156,7 @@ impl TryFrom for Definitions { .map(|variant| { Ok((variant.clone(), Attrs::parse_attrs("event", variant)?)) }) - .collect::>()?; + .collect::>()?; Ok(Self { ident: input.ident, @@ -194,35 +194,30 @@ impl Spanned for Spanning { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "snake_case")] enum SkipAttr { CheckUniqueTypeAndVer, } impl Parse for Spanning { - fn parse(input: ParseStream<'_>) -> Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let ident = syn::Ident::parse(input)?; Ok(Spanning { - item: SkipAttr::from_str(&ident.to_string()) - .map_err(|err| syn::Error::new(ident.span(), err))?, + item: SkipAttr::from_str(&ident.to_string()).map_err(|_| { + syn::Error::new( + ident.span(), + &format!( + "Unknown value. Allowed values: {}", + SkipAttr::VARIANTS.join(", "), + ), + ) + })?, span: ident.span(), }) } } -impl FromStr for SkipAttr { - type Err = &'static str; - - fn from_str(s: &str) -> StdResult { - match s { - "check_unique_type_and_ver" => Ok(Self::CheckUniqueTypeAndVer), - _ => { - Err("Unknown value. Allowed values: check_unique_type_and_ver") - } - } - } -} - #[cfg(test)] mod spec { use super::{derive, quote}; diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 69b68cc..677ca1b 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -25,13 +25,6 @@ mod event; use proc_macro::TokenStream; /// Macro for deriving `arcana::Event`. -/// -/// # Attribute arguments -/// -/// - `#[event(skip(unique_event_type_and_ver))]` — optional -/// -/// Use this value on whole container or particular enum variant to skip check -/// for unique combination of `event_type` and `ver`. #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { event::derive(input.into()) @@ -40,16 +33,6 @@ pub fn derive_event(input: TokenStream) -> TokenStream { } /// Macro for deriving `arcana::VersionedEvent`. -/// -/// # Attribute arguments -/// -/// - `#[event(type = "...")]` — required -/// -/// Value used in `fn event_type()` impl. -/// -/// - `#[event(ver = u16)]` — required -/// -/// Value used in `fn ver()` impl. #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { event::versioned::derive(input.into()) diff --git a/src/lib.rs b/src/lib.rs index 0107783..78924e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,7 @@ pub use arcana_core::{ /// #[derive(Event)] /// enum DuplicatedEvent { /// Any(AnyEvent), -/// File { event: FileEvent }, +/// File(FileEvent), /// } /// ``` /// @@ -90,11 +90,10 @@ pub use arcana_core::{ /// enum DuplicatedEvent { /// Any(AnyEvent), /// #[event(skip(check_unique_type_and_ver))] -/// File { -/// event: FileEvent, -/// }, +/// File(FileEvent), /// } /// ``` +#[cfg(feature = "derive")] pub use arcana_codegen::Event; /// Macro for deriving [`VersionedEvent`](trait@VersionedEvent) on structs. For @@ -119,4 +118,5 @@ pub use arcana_codegen::Event; /// #[event(type = "event", version = 1)] /// struct Event; /// ``` +#[cfg(feature = "derive")] pub use arcana_codegen::VersionedEvent; From c61e8bb519d1f20926834df387b7a83b18030230 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 11:03:39 +0300 Subject: [PATCH 010/104] Use nightly toolchain on CI --- .github/workflows/ci.yml | 2 +- Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04caf7c..8ad8247 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: nightly components: clippy - run: make cargo.lint diff --git a/Makefile b/Makefile index 7377d87..25efff7 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ cargo.fmt: # make cargo.lint cargo.lint: - cargo +stable clippy --workspace -- -D clippy::pedantic -D warnings + cargo +nightly clippy --workspace -- -D clippy::pedantic -D warnings @@ -74,7 +74,7 @@ cargo.lint: # make test [crate=] test: - cargo +stable test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + cargo +nightly test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ --all-features From 32aec5d750dc43aad68affd3bd33e0c06f0d7459 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 11:09:09 +0300 Subject: [PATCH 011/104] Fix CI --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 25efff7..a68a453 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ cargo.doc: ifeq ($(clean),yes) @rm -rf target/doc/ endif - cargo +stable doc $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + cargo doc $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ --all-features \ $(if $(call eq,$(open),no),,--open) @@ -50,7 +50,7 @@ endif # make cargo.fmt [check=(no|yes)] cargo.fmt: - cargo +nightly fmt --all $(if $(call eq,$(check),yes),-- --check,) + cargo fmt --all $(if $(call eq,$(check),yes),-- --check,) # Lint Rust sources with Clippy. @@ -59,7 +59,7 @@ cargo.fmt: # make cargo.lint cargo.lint: - cargo +nightly clippy --workspace -- -D clippy::pedantic -D warnings + cargo clippy --workspace -- -D clippy::pedantic -D warnings @@ -74,7 +74,7 @@ cargo.lint: # make test [crate=] test: - cargo +nightly test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ + cargo test $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ --all-features From aa8f2b43d46e038b5654d52c468a6c880337b6da Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 11:16:28 +0300 Subject: [PATCH 012/104] Remove stable and beta toolchains in testing --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ad8247..a8d833a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,8 +65,6 @@ jobs: - macOS - windows toolchain: - - stable - - beta - nightly runs-on: ${{ matrix.os }}-latest steps: From e815e60e39a6b48ce3103b35601c0ffde1037528 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 11:32:32 +0300 Subject: [PATCH 013/104] CHANGELOG --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8000e21 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +`arcana` changelog +=================== + +All user visible changes to this project will be documented in this file. This project uses [Semantic Versioning 2.0.0]. + + + + +## [0.1.0] · 2021-06-25 +[0.1.0]: /../../tree/v0.1.0 + +### Initially implemented + +- Events + - Traits + - `Event` + - `VersionedEvent` + - `EventSourced` + - `EventInitialised` + - Structs + - `InitialEvent` wrapper + - Proc macros + - `Event` derive + - `VersionedEvent` derive + + + + +[Semantic Versioning 2.0.0]: https://semver.org From b048ca1fd377cf0840ce5cac686c8305cf43fb69 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 12:51:03 +0300 Subject: [PATCH 014/104] Introduce EventName and EventVersion and support them in proc-macros --- codegen/impl/src/event/mod.rs | 16 +++---- codegen/impl/src/event/versioned.rs | 71 +++++++++++++++++++++++++---- core/Cargo.toml | 1 + core/src/event/mod.rs | 61 +++++++++++++++++++++---- core/src/lib.rs | 8 ++-- rust-tollchain.toml | 2 + src/lib.rs | 3 +- 7 files changed, 132 insertions(+), 30 deletions(-) create mode 100644 rust-tollchain.toml diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 3dd2430..c7dd197 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -78,14 +78,14 @@ impl Definitions { #name #ty_generics #where_clause { #[inline(always)] - fn event_type(&self) -> &'static str { + fn event_type(&self) -> ::arcana::EventName { match self { #event_types } } #[inline(always)] - fn ver(&self) -> u16 { + fn ver(&self) -> ::arcana::EventVersion { match self { #event_versions } @@ -237,7 +237,7 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> &'static str { + fn event_type(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { ::arcana::Event::event_type(inner) @@ -249,7 +249,7 @@ mod spec { } #[inline(always)] - fn ver(&self) -> u16 { + fn ver(&self) -> ::arcana::EventVersion { match self { Self::Event1(inner) => { ::arcana::Event::ver(inner) @@ -289,7 +289,7 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> &'static str { + fn event_type(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { ::arcana::Event::event_type(inner) @@ -301,7 +301,7 @@ mod spec { } #[inline(always)] - fn ver(&self) -> u16 { + fn ver(&self) -> ::arcana::EventVersion { match self { Self::Event1(inner) => { ::arcana::Event::ver(inner) @@ -333,7 +333,7 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> &'static str { + fn event_type(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { ::arcana::Event::event_type(inner) @@ -345,7 +345,7 @@ mod spec { } #[inline(always)] - fn ver(&self) -> u16 { + fn ver(&self) -> ::arcana::EventVersion { match self { Self::Event1(inner) => { ::arcana::Event::ver(inner) diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index 48eacb7..bd19751 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -1,6 +1,6 @@ //! Definition of `arcana::VersionedEvent` derive macro for structs. -use std::convert::TryFrom; +use std::{convert::TryFrom, num::NonZeroU16}; use proc_macro2::TokenStream; use quote::quote; @@ -39,13 +39,15 @@ impl Definitions { #name #ty_generics #where_clause { #[inline(always)] - fn event_type() -> &'static str { + fn event_type() -> ::arcana::EventName { #event_type } #[inline(always)] - fn ver() -> u16 { - #event_ver + fn ver() -> ::arcana::EventVersion { + // This is safe, because checked by proc-macro. + #[allow(unsafe_code)] + unsafe { ::arcana::EventVersion::new_unchecked(#event_ver) } } } } @@ -104,10 +106,19 @@ struct Attrs { #[parse(value)] r#type: Option, - #[parse(value)] + #[parse(value, validate = parses_to_non_zero_u16)] version: Option, } +fn parses_to_non_zero_u16<'a>( + val: impl Into>, +) -> Result<()> { + val.into() + .map(syn::LitInt::base10_parse::) + .transpose() + .map(drop) +} + #[cfg(test)] mod spec { use super::{derive, quote}; @@ -123,13 +134,15 @@ mod spec { #[automatically_derived] impl ::arcana::VersionedEvent for Event { #[inline(always)] - fn event_type() -> &'static str { + fn event_type() -> ::arcana::EventName { "event" } #[inline(always)] - fn ver() -> u16 { - 1 + fn ver() -> ::arcana::EventVersion { + // This is safe, because checked by proc-macro. + #[allow(unsafe_code)] + unsafe { ::arcana::EventVersion::new_unchecked(1) } } } @@ -173,6 +186,48 @@ mod spec { ); } + #[test] + fn errors_on_negative_version() { + let input = syn::parse_quote! { + #[event(type = "event", version = -1)] + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!(format!("{}", error), "invalid digit found in string",); + } + + #[test] + fn errors_on_zero_version() { + let input = syn::parse_quote! { + #[event(type = "event", version = 0)] + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "number would be zero for non-zero type", + ); + } + + #[test] + fn errors_on_too_big_version() { + let input = syn::parse_quote! { + #[event(type = "event", version = 4294967295)] + struct Event; + }; + + let error = derive(input).unwrap_err(); + + assert_eq!( + format!("{}", error), + "number too large to fit in target type", + ); + } + #[test] fn errors_on_enum() { let input = syn::parse_quote! { diff --git a/core/Cargo.toml b/core/Cargo.toml index 862f968..92f5ef1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -6,3 +6,4 @@ edition = "2018" [dependencies] derive_more = "0.99" ref-cast = "1.0" +safety-guard = "0.1" diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index d7eabb4..9adfc63 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -1,6 +1,10 @@ //! Event related definitions. +use std::{convert::TryFrom, num::NonZeroU16}; + +use derive_more::{Display, Into}; use ref_cast::RefCast; +use safety_guard::safety; /// [Event Sourcing] event that describes something that has occurred (happened /// fact). @@ -10,14 +14,16 @@ use ref_cast::RefCast; /// /// [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub trait Event { - /// Returns type of this [`Event`]. + /// Returns [`Name`] of this [`Event`]. /// /// _Note:_ This should effectively be a constant value, and should never /// change. - fn event_type(&self) -> &'static str; + #[must_use] + fn event_type(&self) -> Name; - /// Returns version of this [`Event`]. - fn ver(&self) -> u16; + /// Returns [`Version`] of this [`Event`]. + #[must_use] + fn ver(&self) -> Version; } /// Versioned [`Event`]. @@ -28,22 +34,57 @@ pub trait Event { /// version of [`Event`] to implement trait [`From`] its previous versions, so /// they can be automatically transformed into the latest actual version of pub trait Versioned { - /// Returns type of this [`Event`]. + /// Returns [`Name`] of this [`Event`]. /// /// _Note:_ This should effectively be a constant value, and should never /// change. - fn event_type() -> &'static str; + #[must_use] + fn event_type() -> Name; + + /// Returns [`Version`] of this [`Event`]. + #[must_use] + fn ver() -> Version; +} + +/// Fully qualified name of an [`Event`]. +pub type Name = &'static str; + +/// Revision number of an [`Event`]. +#[derive( + Clone, Copy, Debug, Display, Eq, Hash, Into, Ord, PartialEq, PartialOrd, +)] +pub struct Version(NonZeroU16); - /// Returns version of this [`Event`]. - fn ver() -> u16; +impl Version { + /// Creates a new [`Version`] out of the given `val`ue. + /// + /// The given value should not be `0` (zero) and fit into [`u16`] size. + #[inline] + #[must_use] + pub fn try_new(val: N) -> Option + where + u16: TryFrom, + { + Some(Self(NonZeroU16::new(u16::try_from(val).ok()?)?)) + } + + /// Creates a new [`Version`] out of the given `val`ue without checking its + /// invariants. + #[allow(unsafe_code)] + #[inline] + #[must_use] + #[safety(ne(val, 0), "The given `val`ue must not be `0` (zero).")] + pub unsafe fn new_unchecked(val: u16) -> Self { + Self(NonZeroU16::new_unchecked(val)) + } } impl Event for Ev { - fn event_type(&self) -> &'static str { + fn event_type(&self) -> Name { ::event_type() } - fn ver(&self) -> u16 { + fn ver(&self) -> Version { ::ver() } } diff --git a/core/src/lib.rs b/core/src/lib.rs index ff223b9..39ee9da 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,9 +5,10 @@ rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links, trivial_casts, - trivial_numeric_casts + trivial_numeric_casts, + unsafe_code )] -#![forbid(non_ascii_idents, unsafe_code)] +#![forbid(non_ascii_idents)] #![warn( deprecated_in_future, missing_copy_implementations, @@ -25,5 +26,6 @@ mod event; #[doc(inline)] pub use event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, - Sourced as EventSourced, Versioned as VersionedEvent, + Name as EventName, Sourced as EventSourced, Version as EventVersion, + Versioned as VersionedEvent, }; diff --git a/rust-tollchain.toml b/rust-tollchain.toml new file mode 100644 index 0000000..fa8e0d5 --- /dev/null +++ b/rust-tollchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2021-08-17" diff --git a/src/lib.rs b/src/lib.rs index 78924e4..fde5b62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,8 @@ pub mod private; #[doc(inline)] pub use arcana_core::{ - Event, EventInitialized, EventSourced, InitialEvent, VersionedEvent, + Event, EventInitialized, EventName, EventSourced, EventVersion, + InitialEvent, VersionedEvent, }; /// Macro for deriving [`Event`](trait@Event) on enums. For structs consider From c6f1ff9d9329fcf3922d0ea36a3d69521ea18b01 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 12:52:16 +0300 Subject: [PATCH 015/104] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8000e21..13869ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All user visible changes to this project will be documented in this file. This p - `EventInitialised` - Structs - `InitialEvent` wrapper + - `EventVersion` - Proc macros - `Event` derive - `VersionedEvent` derive From c47737830dddc1b3723084ec0561558f711836e4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 17 Aug 2021 13:21:57 +0300 Subject: [PATCH 016/104] Some corrections [skip ci] --- src/private.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/private.rs b/src/private.rs index 42b7b31..e8d6605 100644 --- a/src/private.rs +++ b/src/private.rs @@ -97,12 +97,11 @@ pub mod unique_event_type_and_ver { types: [Option<(&str, u16)>; N], ) -> bool { const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - if l.len() != r.len() { return false; } + let (l, r) = (l.as_bytes(), r.as_bytes()); let mut i = 0; while i < l.len() { if l[i] != r[i] { From 4bd1c854acda440cf2eb9759cfdaecb7952dd3df Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 13:59:12 +0300 Subject: [PATCH 017/104] Replace event_type() with name() everywhere [skip ci] --- codegen/impl/src/event/mod.rs | 58 ++++++++++---------- codegen/impl/src/event/versioned.rs | 50 ++++++++--------- core/src/event/mod.rs | 8 +-- examples/event.rs | 8 +-- src/lib.rs | 34 ++++++------ src/private.rs | 85 +++++++++++++++-------------- 6 files changed, 125 insertions(+), 118 deletions(-) diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index c7dd197..f7a5afa 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -24,7 +24,7 @@ pub(crate) fn derive(input: TokenStream) -> syn::Result { } #[derive(ToTokens)] -#[to_tokens(append(impl_from, unique_event_type_and_ver))] +#[to_tokens(append(impl_from, unique_event_name_and_ver))] struct Definitions { ident: syn::Ident, generics: syn::Generics, @@ -37,7 +37,7 @@ impl Definitions { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let (event_types, event_versions): (TokenStream, TokenStream) = self + let (event_names, event_versions): (TokenStream, TokenStream) = self .variants .iter() .map(|(variant, _)| { @@ -66,7 +66,7 @@ impl Definitions { }; ( - generate_variant(quote! { event_type }), + generate_variant(quote! { name }), generate_variant(quote! { ver }), ) }) @@ -78,9 +78,9 @@ impl Definitions { #name #ty_generics #where_clause { #[inline(always)] - fn event_type(&self) -> ::arcana::EventName { + fn name(&self) -> ::arcana::EventName { match self { - #event_types + #event_names } } @@ -94,8 +94,8 @@ impl Definitions { } } - fn unique_event_type_and_ver(&self) -> TokenStream { - if self.attrs.skip_check_unique_type_and_ver() { + fn unique_event_name_and_ver(&self) -> TokenStream { + if self.attrs.skip_check_unique_name_and_ver() { return TokenStream::new(); } @@ -107,7 +107,7 @@ impl Definitions { .variants .iter() .filter_map(|(variant, attr)| { - (!attr.skip_check_unique_type_and_ver()).then(|| { + (!attr.skip_check_unique_name_and_ver()).then(|| { let ty = &variant.fields.iter().next().unwrap().ty; quote! { #ty, } }) @@ -116,12 +116,12 @@ impl Definitions { quote! { impl #impl_generics #name #ty_generics #where_clause { - ::arcana::unique_event_type_and_ver_for_enum!( + ::arcana::unique_event_name_and_ver_for_enum!( #max, #event_variants ); } - ::arcana::unique_event_type_and_ver_check!(#name); + ::arcana::unique_event_name_and_ver_check!(#name); } } } @@ -174,10 +174,10 @@ struct Attrs { } impl Attrs { - fn skip_check_unique_type_and_ver(&self) -> bool { + fn skip_check_unique_name_and_ver(&self) -> bool { matches!( self.skip.as_ref().map(|sp| sp.item), - Some(SkipAttr::CheckUniqueTypeAndVer), + Some(SkipAttr::CheckUniqueNameAndVer), ) } } @@ -197,7 +197,7 @@ impl Spanned for Spanning { #[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] #[strum(serialize_all = "snake_case")] enum SkipAttr { - CheckUniqueTypeAndVer, + CheckUniqueNameAndVer, } impl Parse for Spanning { @@ -237,13 +237,13 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> ::arcana::EventName { + fn name(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { - ::arcana::Event::event_type(inner) + ::arcana::Event::name(inner) } Self::Event2 { event } => { - ::arcana::Event::event_type(event) + ::arcana::Event::name(event) } } } @@ -262,12 +262,12 @@ mod spec { } impl Event { - ::arcana::unique_event_type_and_ver_for_enum!( + ::arcana::unique_event_name_and_ver_for_enum!( 100000usize, EventUnnamend, EventNamed, ); } - ::arcana::unique_event_type_and_ver_check!(Event); + ::arcana::unique_event_name_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); @@ -276,7 +276,7 @@ mod spec { #[test] fn skip_unique_check_on_container() { let input = syn::parse_quote! { - #[event(skip(check_unique_type_and_ver))] + #[event(skip(check_unique_name_and_ver))] enum Event { Event1(EventUnnamend), Event2 { @@ -289,13 +289,13 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> ::arcana::EventName { + fn name(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { - ::arcana::Event::event_type(inner) + ::arcana::Event::name(inner) } Self::Event2 { event } => { - ::arcana::Event::event_type(event) + ::arcana::Event::name(event) } } } @@ -321,7 +321,7 @@ mod spec { fn skip_unique_check_on_variant() { let input = syn::parse_quote! { enum Event { - #[event(skip(check_unique_type_and_ver))] + #[event(skip(check_unique_name_and_ver))] Event1(EventUnnamend), Event2 { event: EventNamed, @@ -333,13 +333,13 @@ mod spec { #[automatically_derived] impl ::arcana::Event for Event { #[inline(always)] - fn event_type(&self) -> ::arcana::EventName { + fn name(&self) -> ::arcana::EventName { match self { Self::Event1(inner) => { - ::arcana::Event::event_type(inner) + ::arcana::Event::name(inner) } Self::Event2 { event } => { - ::arcana::Event::event_type(event) + ::arcana::Event::name(event) } } } @@ -358,12 +358,12 @@ mod spec { } impl Event { - ::arcana::unique_event_type_and_ver_for_enum!( + ::arcana::unique_event_name_and_ver_for_enum!( 100000usize, EventNamed, ); } - ::arcana::unique_event_type_and_ver_check!(Event); + ::arcana::unique_event_name_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); @@ -402,7 +402,7 @@ mod spec { assert_eq!( format!("{}", error), - "Unknown value. Allowed values: check_unique_type_and_ver", + "Unknown value. Allowed values: check_unique_name_and_ver", ); } diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index bd19751..cc8be38 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -18,11 +18,11 @@ pub(crate) fn derive(input: TokenStream) -> Result { } #[derive(ToTokens)] -#[to_tokens(append(impl_from, unique_event_type_and_ver))] +#[to_tokens(append(impl_from, unique_event_name_and_ver))] struct Definitions { ident: syn::Ident, generics: syn::Generics, - event_type: syn::LitStr, + event_name: syn::LitStr, event_ver: syn::LitInt, } @@ -31,7 +31,7 @@ impl Definitions { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let (event_type, event_ver) = (&self.event_type, &self.event_ver); + let (event_name, event_ver) = (&self.event_name, &self.event_ver); quote! { #[automatically_derived] @@ -39,8 +39,8 @@ impl Definitions { #name #ty_generics #where_clause { #[inline(always)] - fn event_type() -> ::arcana::EventName { - #event_type + fn name() -> ::arcana::EventName { + #event_name } #[inline(always)] @@ -53,17 +53,17 @@ impl Definitions { } } - fn unique_event_type_and_ver(&self) -> TokenStream { + fn unique_event_name_and_ver(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let (event_type, event_ver) = (&self.event_type, &self.event_ver); + let (event_name, event_ver) = (&self.event_name, &self.event_ver); let max = MAX_UNIQUE_EVENTS; quote! { impl #impl_generics #name #ty_generics #where_clause { - ::arcana::unique_event_type_and_ver_for_struct!( - #max, #event_type, #event_ver + ::arcana::unique_event_name_and_ver_for_struct!( + #max, #event_name, #event_ver ); } } @@ -82,12 +82,12 @@ impl TryFrom for Definitions { } let attrs = Attrs::parse_attrs("event", &input)?; - let (event_type, event_ver) = match (attrs.r#type, attrs.version) { - (Some(event_type), Some(event_ver)) => (event_type, event_ver), + let (event_name, event_ver) = match (attrs.name, attrs.version) { + (Some(event_name), Some(event_ver)) => (event_name, event_ver), _ => { return Err(syn::Error::new_spanned( input, - "`type` and `version` arguments expected", + "`name` and `version` arguments expected", )) } }; @@ -95,7 +95,7 @@ impl TryFrom for Definitions { Ok(Self { ident: input.ident, generics: input.generics, - event_type, + event_name, event_ver, }) } @@ -104,7 +104,7 @@ impl TryFrom for Definitions { #[derive(Default, ParseAttrs)] struct Attrs { #[parse(value)] - r#type: Option, + name: Option, #[parse(value, validate = parses_to_non_zero_u16)] version: Option, @@ -126,7 +126,7 @@ mod spec { #[test] fn derives_struct_impl() { let input = syn::parse_quote! { - #[event(type = "event", version = 1)] + #[event(name = "event", version = 1)] struct Event; }; @@ -134,7 +134,7 @@ mod spec { #[automatically_derived] impl ::arcana::VersionedEvent for Event { #[inline(always)] - fn event_type() -> ::arcana::EventName { + fn name() -> ::arcana::EventName { "event" } @@ -147,7 +147,7 @@ mod spec { } impl Event { - ::arcana::unique_event_type_and_ver_for_struct!( + ::arcana::unique_event_name_and_ver_for_struct!( 100000usize, "event", 1 ); } @@ -157,7 +157,7 @@ mod spec { } #[test] - fn type_argument_is_expected() { + fn name_argument_is_expected() { let input = syn::parse_quote! { #[event(version = 1)] struct Event; @@ -167,14 +167,14 @@ mod spec { assert_eq!( format!("{}", error), - "`type` and `version` arguments expected", + "`name` and `version` arguments expected", ); } #[test] fn version_argument_is_expected() { let input = syn::parse_quote! { - #[event(type = "event")] + #[event(name = "event")] struct Event; }; @@ -182,14 +182,14 @@ mod spec { assert_eq!( format!("{}", error), - "`type` and `version` arguments expected", + "`name` and `version` arguments expected", ); } #[test] fn errors_on_negative_version() { let input = syn::parse_quote! { - #[event(type = "event", version = -1)] + #[event(name = "event", version = -1)] struct Event; }; @@ -201,7 +201,7 @@ mod spec { #[test] fn errors_on_zero_version() { let input = syn::parse_quote! { - #[event(type = "event", version = 0)] + #[event(name = "event", version = 0)] struct Event; }; @@ -216,7 +216,7 @@ mod spec { #[test] fn errors_on_too_big_version() { let input = syn::parse_quote! { - #[event(type = "event", version = 4294967295)] + #[event(name = "event", version = 4294967295)] struct Event; }; @@ -231,7 +231,7 @@ mod spec { #[test] fn errors_on_enum() { let input = syn::parse_quote! { - #[event(type = "event", version = 1)] + #[event(name = "event", version = 1)] enum Event { Event1(Event1), } diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index 9adfc63..afa2742 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -19,7 +19,7 @@ pub trait Event { /// _Note:_ This should effectively be a constant value, and should never /// change. #[must_use] - fn event_type(&self) -> Name; + fn name(&self) -> Name; /// Returns [`Version`] of this [`Event`]. #[must_use] @@ -39,7 +39,7 @@ pub trait Versioned { /// _Note:_ This should effectively be a constant value, and should never /// change. #[must_use] - fn event_type() -> Name; + fn name() -> Name; /// Returns [`Version`] of this [`Event`]. #[must_use] @@ -80,8 +80,8 @@ impl Version { } impl Event for Ev { - fn event_type(&self) -> Name { - ::event_type() + fn name(&self) -> Name { + ::name() } fn ver(&self) -> Version { diff --git a/examples/event.rs b/examples/event.rs index 371b310..14d189b 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -1,11 +1,11 @@ use arcana::{Event, VersionedEvent}; #[derive(VersionedEvent)] -#[event(type = "chat", version = 1)] +#[event(name = "chat", version = 1)] struct ChatEvent; #[derive(VersionedEvent)] -#[event(type = "file", version = 1)] +#[event(name = "file", version = 1)] struct FileEvent; #[derive(Event)] @@ -16,8 +16,8 @@ enum AnyEvent { fn main() { let ev = AnyEvent::Chat(ChatEvent); - assert_eq!(ev.event_type(), "chat"); + assert_eq!(ev.name(), "chat"); let ev = AnyEvent::File { event: FileEvent }; - assert_eq!(ev.event_type(), "file"); + assert_eq!(ev.name(), "file"); } diff --git a/src/lib.rs b/src/lib.rs index fde5b62..99dcf70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,17 +32,19 @@ pub use arcana_core::{ /// Macro for deriving [`Event`](trait@Event) on enums. For structs consider /// [`VersionedEvent`](macro@VersionedEvent). /// -/// This macro ensures that every combination of `event_type` and `ver` are -/// unique. The only limitation is that every underlying -/// [`Event`](trait@Event) or [`VersionedEvent`](trait@VersionedEvent) impls -/// should be generated with proc macros. +/// This macro ensures that every combination of [`Event::name()`](trait@Event) +/// and [`Event::ver()`](trait@Event) are unique. The only limitation is that +/// every underlying [`Event`](trait@Event) or +/// [`VersionedEvent`](trait@VersionedEvent) impls should be generated with proc +/// macros. /// /// # Attribute arguments /// -/// - `#[event(skip(unique_event_type_and_ver))]` — optional +/// - `#[event(skip(check_unique_name_and_ver))]` — optional /// /// Use this value on whole container or particular enum variant to skip check -/// for unique combination of `event_type` and `ver`. +/// for unique combination of [`Event::name()`](trait@Event) and +/// [`Event::ver()`](trait@Event). /// /// # Examples /// @@ -50,11 +52,11 @@ pub use arcana_core::{ /// # use arcana::{Event, VersionedEvent}; /// # /// #[derive(VersionedEvent)] -/// #[event(type = "chat", version = 1)] +/// #[event(name = "chat", version = 1)] /// struct ChatEvent; /// /// #[derive(VersionedEvent)] -/// #[event(type = "file", version = 1)] +/// #[event(name = "file", version = 1)] /// struct FileEvent; /// /// #[derive(Event)] @@ -74,11 +76,11 @@ pub use arcana_core::{ /// # use arcana::{Event, VersionedEvent}; /// # /// # #[derive(VersionedEvent)] -/// # #[event(type = "chat", version = 1)] +/// # #[event(name = "chat", version = 1)] /// # struct ChatEvent; /// # /// # #[derive(VersionedEvent)] -/// # #[event(type = "file", version = 1)] +/// # #[event(name = "file", version = 1)] /// # struct FileEvent; /// # /// # #[derive(Event)] @@ -90,7 +92,7 @@ pub use arcana_core::{ /// #[derive(Event)] /// enum DuplicatedEvent { /// Any(AnyEvent), -/// #[event(skip(check_unique_type_and_ver))] +/// #[event(skip(check_unique_name_and_ver))] /// File(FileEvent), /// } /// ``` @@ -102,13 +104,13 @@ pub use arcana_codegen::Event; /// /// # Attribute arguments /// -/// - `#[event(type = "...")]` — required +/// - `#[event(name = "...")]` — required /// -/// Value used in `fn event_type()` impl. +/// Value used in [`VersionedEvent::name()`](trait@VersionedEvent) impl. /// -/// - `#[event(ver = u16)]` — required +/// - `#[event(ver = NonZeroU16)]` — required /// -/// Value used in `fn ver()` impl. +/// Value used in [`VersionedEvent::ver()`](trait@VersionedEvent) impl. /// /// # Examples /// @@ -116,7 +118,7 @@ pub use arcana_codegen::Event; /// # use arcana::VersionedEvent; /// # /// #[derive(VersionedEvent)] -/// #[event(type = "event", version = 1)] +/// #[event(name = "event", version = 1)] /// struct Event; /// ``` #[cfg(feature = "derive")] diff --git a/src/private.rs b/src/private.rs index e8d6605..91c424c 100644 --- a/src/private.rs +++ b/src/private.rs @@ -3,12 +3,12 @@ pub use static_assertions as sa; /// Utils for ensuring that every [`Event`] variant has a unique combination of -/// `event_type` and `ver`. +/// [`Event::name()`] and [`Event::ver()`]. /// /// # Explanation /// /// Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates -/// `const fn __arcana_event_types() -> [Option<(&'static str, u16)>; size]` +/// `const fn __arcana_events() -> [Option<(&'static str, u16)>; size]` /// const function. Size of outputted array determines max count of unique /// [`VersionedEvent`]s inside [`Event`] and is tweakable inside /// `arcana_codegen_impl` crate (default is `100_000` which should be plenty). @@ -17,36 +17,39 @@ pub use static_assertions as sa; /// /// - Structs /// -/// [`unique_event_type_and_ver_for_struct`] macro generates function, which +/// [`unique_event_name_and_ver_for_struct`] macro generates function, which /// returns array with only first occupied entry. The rest of them are /// [`None`]. /// /// - Enums /// -/// [`unique_event_type_and_ver_for_enum`] macro generates function, which +/// [`unique_event_name_and_ver_for_enum`] macro generates function, which /// glues subtypes arrays into single continues array. First `n` entries are /// occupied, while the rest of them are [`None`], where `n` is the number of /// [`VersionedEvent`]s. As structs deriving [`VersionedEvent`] and enums -/// deriving [`Event`] have the same output by `__arcana_event_types()` const +/// deriving [`Event`] have the same output by `__arcana_events()` const /// function, top-level enum variants can have different levels of nesting. /// -/// [`unique_event_type_and_ver_check`] macro generates [`const_assert`] -/// check, which fails in case of duplicated `event_type` and `ver`. +/// [`unique_event_name_and_ver_check`] macro generates [`const_assert`] +/// check, which fails in case of duplicated [`Event::name()`] and +/// [`Event::ver()`]. /// /// /// [`const_assert`]: static_assertions::const_assert /// [`Event`]: trait@crate::Event +/// [`Event::name()`]: trait@crate::Event::name() +/// [`Event::ver()`]: trait@crate::Event::ver() /// [`VersionedEvent`]: trait@crate::VersionedEvent -pub mod unique_event_type_and_ver { +pub mod unique_event_name_and_ver { #[doc(hidden)] #[macro_export] - macro_rules! unique_event_type_and_ver_for_struct { - ($max_events:literal, $event_type:literal, $event_ver:literal) => { + macro_rules! unique_event_name_and_ver_for_struct { + ($max_events:literal, $event_name:literal, $event_ver:literal) => { #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_event_types( + pub const fn __arcana_events( ) -> [Option<(&'static str, u16)>; $max_events] { let mut res = [None; $max_events]; - res[0] = Some(($event_type, $event_ver)); + res[0] = Some(($event_name, $event_ver)); res } }; @@ -54,10 +57,10 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[macro_export] - macro_rules! unique_event_type_and_ver_for_enum { - ($max_events: literal, $($event_type: ty),* $(,)?) => { + macro_rules! unique_event_name_and_ver_for_enum { + ($max_events: literal, $($event_name: ty),* $(,)?) => { #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_event_types() -> + pub const fn __arcana_events() -> [Option<(&'static str, u16)>; $max_events] { let mut res = [None; $max_events]; @@ -65,7 +68,7 @@ pub mod unique_event_type_and_ver { let mut global = 0; $({ - let ev = <$event_type>::__arcana_event_types(); + let ev = <$event_name>::__arcana_events(); let mut local = 0; while let Some(s) = ev[local] { res[global] = Some(s); @@ -81,11 +84,11 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[macro_export] - macro_rules! unique_event_type_and_ver_check { + macro_rules! unique_event_name_and_ver_check { ($event:ty) => { $crate::private::sa::const_assert!( - $crate::private::unique_event_type_and_ver::all_unique( - <$event>::__arcana_event_types() + $crate::private::unique_event_name_and_ver::all_unique( + <$event>::__arcana_events() ) ); }; @@ -94,30 +97,13 @@ pub mod unique_event_type_and_ver { #[doc(hidden)] #[must_use] pub const fn all_unique( - types: [Option<(&str, u16)>; N], + events: [Option<(&str, u16)>; N], ) -> bool { - const fn str_eq(l: &str, r: &str) -> bool { - if l.len() != r.len() { - return false; - } - - let (l, r) = (l.as_bytes(), r.as_bytes()); - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true - } - let mut outer = 0; - while let Some((outer_type, outer_ver)) = types[outer] { + while let Some((outer_name, outer_ver)) = events[outer] { let mut inner = outer + 1; - while let Some((inner_type, inner_ver)) = types[inner] { - if str_eq(inner_type, outer_type) && inner_ver == outer_ver { + while let Some((inner_name, inner_ver)) = events[inner] { + if str_eq(inner_name, outer_name) && inner_ver == outer_ver { return false; } inner += 1; @@ -127,4 +113,23 @@ pub mod unique_event_type_and_ver { true } + + #[doc(hidden)] + #[must_use] + const fn str_eq(l: &str, r: &str) -> bool { + if l.len() != r.len() { + return false; + } + + let (l, r) = (l.as_bytes(), r.as_bytes()); + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } } From 8bc5e8c8979c02d4a54ec3cbeb797381e94b3740 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 14:28:22 +0300 Subject: [PATCH 018/104] Add es feature-gates [skip ci] --- Cargo.toml | 5 +++-- core/Cargo.toml | 4 ++++ core/src/{event => es}/mod.rs | 21 ++++++++++----------- core/src/lib.rs | 6 ++++-- src/lib.rs | 2 ++ 5 files changed, 23 insertions(+), 15 deletions(-) rename core/src/{event => es}/mod.rs (87%) diff --git a/Cargo.toml b/Cargo.toml index 3eec2ed..2f5bd1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,9 @@ version = "0.1.0" edition = "2018" [features] -default = ["derive"] -derive = [] +default = ["derive", "es"] +derive = ["es"] +es = ["arcana-core/es"] [dependencies] arcana-core = { version = "0.1.0", path = "./core" } diff --git a/core/Cargo.toml b/core/Cargo.toml index 92f5ef1..13acfd0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -3,6 +3,10 @@ name = "arcana-core" version = "0.1.0" edition = "2018" +[features] +default = ["es"] +es = [] + [dependencies] derive_more = "0.99" ref-cast = "1.0" diff --git a/core/src/event/mod.rs b/core/src/es/mod.rs similarity index 87% rename from core/src/event/mod.rs rename to core/src/es/mod.rs index afa2742..9d8a1ac 100644 --- a/core/src/event/mod.rs +++ b/core/src/es/mod.rs @@ -1,4 +1,6 @@ -//! Event related definitions. +//! [Event Sourcing] related definitions. +//! +//! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html use std::{convert::TryFrom, num::NonZeroU16}; @@ -9,9 +11,6 @@ use safety_guard::safety; /// [Event Sourcing] event that describes something that has occurred (happened /// fact). /// -/// A sequence of [`Event`]s may represent a concrete versioned state of an -/// Aggregate. -/// /// [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub trait Event { /// Returns [`Name`] of this [`Event`]. @@ -95,10 +94,10 @@ pub trait Sourced { fn apply(&mut self, event: &Ev); } -impl> Sourced for Option { +impl> Sourced for Option { fn apply(&mut self, event: &Ev) { - if let Some(agg) = self { - agg.apply(event); + if let Some(state) = self { + state.apply(event); } } } @@ -110,14 +109,14 @@ pub trait Initialized { } /// Wrapper-type intended for [`Event`]s that can initialize [`Sourced`] items. -#[derive(Debug, RefCast)] +#[derive(Clone, Copy, Debug, Display, RefCast)] #[repr(transparent)] pub struct Initial(pub Ev); -impl> Sourced> - for Option +impl> Sourced> + for Option { fn apply(&mut self, event: &Initial) { - *self = Some(Agg::init(&event.0)); + *self = Some(S::init(&event.0)); } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 39ee9da..6d1e692 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -21,10 +21,12 @@ unused_results )] -mod event; +#[cfg(feature = "es")] +mod es; #[doc(inline)] -pub use event::{ +#[cfg(feature = "es")] +pub use es::{ Event, Initial as InitialEvent, Initialized as EventInitialized, Name as EventName, Sourced as EventSourced, Version as EventVersion, Versioned as VersionedEvent, diff --git a/src/lib.rs b/src/lib.rs index 99dcf70..3eb2af3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,9 +21,11 @@ )] #[doc(hidden)] +#[cfg(feature = "es")] pub mod private; #[doc(inline)] +#[cfg(feature = "es")] pub use arcana_core::{ Event, EventInitialized, EventName, EventSourced, EventVersion, InitialEvent, VersionedEvent, From 418b2336c466014bd5043dda3ae4d1a6f6233f24 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 14:29:54 +0300 Subject: [PATCH 019/104] Split derive and es feature-gates [skip ci] --- Cargo.toml | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f5bd1d..dcbafdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [features] default = ["derive", "es"] -derive = ["es"] +derive = [] es = ["arcana-core/es"] [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 3eb2af3..b2db5b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ pub use arcana_core::{ /// File(FileEvent), /// } /// ``` -#[cfg(feature = "derive")] +#[cfg(all(feature = "derive", feature = "es"))] pub use arcana_codegen::Event; /// Macro for deriving [`VersionedEvent`](trait@VersionedEvent) on structs. For @@ -123,5 +123,5 @@ pub use arcana_codegen::Event; /// #[event(name = "event", version = 1)] /// struct Event; /// ``` -#[cfg(feature = "derive")] +#[cfg(all(feature = "derive", feature = "es"))] pub use arcana_codegen::VersionedEvent; From 683456bb7b0f199ef9f924c0e8e5933cf56405d5 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 14:51:19 +0300 Subject: [PATCH 020/104] Use arcana-core crate for arcana-codegen-impl docs [skip ci] --- codegen/impl/Cargo.toml | 1 + codegen/impl/src/event/mod.rs | 8 ++++++-- codegen/impl/src/event/versioned.rs | 8 ++++++-- codegen/impl/src/lib.rs | 8 ++++++-- rust-tollchain.toml | 2 -- 5 files changed, 19 insertions(+), 8 deletions(-) delete mode 100644 rust-tollchain.toml diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index d133a15..608fecd 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" proc-macro = true [dependencies] +arcana-core = { version = "0.1.0", path = "../../core" } proc-macro2 = "1.0" quote = "1.0" strum = { version = "0.21", features = ["derive"] } diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index f7a5afa..94cf56f 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -1,4 +1,6 @@ -//! Definition of `arcana::Event` derive macro for enums. +//! Definition of [`Event`] derive macro for enums. +//! +//! [`Event`]: arcana_core::Event pub(crate) mod versioned; @@ -15,7 +17,9 @@ use synthez::{ParseAttrs, ToTokens}; const MAX_UNIQUE_EVENTS: usize = 100_000; -/// Derives `arcana::Event` for enum. +/// Derives [`Event`] for enum. +/// +/// [`Event`]: arcana_core::Event pub(crate) fn derive(input: TokenStream) -> syn::Result { let input: syn::DeriveInput = syn::parse2(input)?; let definitions = Definitions::try_from(input)?; diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index cc8be38..2ad4c65 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -1,4 +1,6 @@ -//! Definition of `arcana::VersionedEvent` derive macro for structs. +//! Definition of [`VersionedEvent`] derive macro for structs. +//! +//! [`VersionedEvent`]: arcana_core::VersionedEvent use std::{convert::TryFrom, num::NonZeroU16}; @@ -9,7 +11,9 @@ use synthez::{ParseAttrs, ToTokens}; use super::MAX_UNIQUE_EVENTS; -/// Derives `arcana::VersionedEvent` for struct. +/// Derives [`VersionedEvent`] for struct. +/// +/// [`VersionedEvent`]: arcana_core::VersionedEvent pub(crate) fn derive(input: TokenStream) -> Result { let input = syn::parse2::(input)?; let definitions = Definitions::try_from(input)?; diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 677ca1b..018e182 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -24,7 +24,9 @@ mod event; use proc_macro::TokenStream; -/// Macro for deriving `arcana::Event`. +/// Macro for deriving [`Event`]. +/// +/// [`Event`]: arcana_core::Event #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { event::derive(input.into()) @@ -32,7 +34,9 @@ pub fn derive_event(input: TokenStream) -> TokenStream { .into() } -/// Macro for deriving `arcana::VersionedEvent`. +/// Macro for deriving [`VersionedEvent`]. +/// +/// [`VersionedEvent`]: arcana_core::VersionedEvent #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { event::versioned::derive(input.into()) diff --git a/rust-tollchain.toml b/rust-tollchain.toml deleted file mode 100644 index fa8e0d5..0000000 --- a/rust-tollchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly-2021-08-17" From 7a8ceb521aa0271762d40623733c8280d3d6e572 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 15:12:11 +0300 Subject: [PATCH 021/104] WIP [skip ci] --- codegen/impl/src/event/mod.rs | 140 +++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 94cf56f..12f96cf 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -27,12 +27,101 @@ pub(crate) fn derive(input: TokenStream) -> syn::Result { Ok(quote! { #definitions }) } +/// Attributes for [`Event`] derive macro. +/// +/// [`Event`]: arcana_core::Event +#[derive(Default, ParseAttrs)] +struct Attrs { + /// `#[event(skip(...))` attribute. + #[parse(value)] + skip: Option>, +} + +impl Attrs { + /// Checks whether variant or whole container shouldn't be checked for + /// [`Event::name()`] and [`Event::ver()`] uniqueness. + /// + /// [`Event::name()`]: arcana_core::Event::name() + /// [`Event::ver()`]: arcana_core::Event::ver() + fn skip_check_unique_name_and_ver(&self) -> bool { + matches!( + self.skip.as_ref().map(|sp| sp.item), + Some(SkipAttr::CheckUniqueNameAndVer), + ) + } +} + +/// Wrapper for storing [`Span`]. +/// +/// We don't use one from [`synthez`], as we can't derive [`Parse`] with our `T` +/// inside. +#[derive(Clone, Debug)] +struct Spanning { + item: T, + span: Span, +} + +impl Spanned for Spanning { + fn span(&self) -> Span { + self.span + } +} + +/// Inner value for `#[event(skip(...))]` attribute. +#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "snake_case")] +enum SkipAttr { + /// Variant for skipping uniqueness check of [`Event::name()`] and + /// [`Event::ver()`]. + /// + /// [`Event::name()`]: arcana_core::Event::name() + /// [`Event::ver()`]: arcana_core::Event::ver() + CheckUniqueNameAndVer, +} + +impl Parse for Spanning { + fn parse(input: ParseStream<'_>) -> syn::Result { + let ident = syn::Ident::parse(input)?; + Ok(Spanning { + item: SkipAttr::from_str(&ident.to_string()).map_err(|_| { + syn::Error::new( + ident.span(), + &format!( + "Unknown value. Allowed values: {}", + SkipAttr::VARIANTS.join(", "), + ), + ) + })?, + span: ident.span(), + }) + } +} + +/// Definition of [`Event`] derive macro. +/// +/// [`Event`]: arcana_core::Event #[derive(ToTokens)] #[to_tokens(append(impl_from, unique_event_name_and_ver))] struct Definitions { + /// Enum's [`Ident`]. + /// + /// [`Ident`]: syn::Ident ident: syn::Ident, + + /// Enum's [`Generics`]. + /// + /// [`Generics`]: syn::Generics generics: syn::Generics, + + /// Enum's [`Variant`]s alongside with parsed [`Attrs`]. + /// + /// Every [`Variant`] has exactly 1 [`Field`]. + /// + /// [`Field`]: syn::Field + /// [`Variant`]: syn::Variant variants: Vec<(syn::Variant, Attrs)>, + + /// Enum's top-level [`Attrs`]. attrs: Attrs, } @@ -171,57 +260,6 @@ impl TryFrom for Definitions { } } -#[derive(Default, ParseAttrs)] -struct Attrs { - #[parse(value)] - skip: Option>, -} - -impl Attrs { - fn skip_check_unique_name_and_ver(&self) -> bool { - matches!( - self.skip.as_ref().map(|sp| sp.item), - Some(SkipAttr::CheckUniqueNameAndVer), - ) - } -} - -#[derive(Clone, Debug)] -struct Spanning { - item: T, - span: Span, -} - -impl Spanned for Spanning { - fn span(&self) -> Span { - self.span - } -} - -#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] -#[strum(serialize_all = "snake_case")] -enum SkipAttr { - CheckUniqueNameAndVer, -} - -impl Parse for Spanning { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = syn::Ident::parse(input)?; - Ok(Spanning { - item: SkipAttr::from_str(&ident.to_string()).map_err(|_| { - syn::Error::new( - ident.span(), - &format!( - "Unknown value. Allowed values: {}", - SkipAttr::VARIANTS.join(", "), - ), - ) - })?, - span: ident.span(), - }) - } -} - #[cfg(test)] mod spec { use super::{derive, quote}; From 8a9b4ec3f2b9e1549b1bab528bb53bfc64caa8ba Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 15:16:33 +0300 Subject: [PATCH 022/104] Cover Event derive macro with docs [skip ci] --- codegen/impl/src/event/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 12f96cf..65ff75e 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -126,6 +126,10 @@ struct Definitions { } impl Definitions { + /// Generates code to derive [`Event`] by simply matching over every enum + /// variant, which is expected to be itself [`Event`] deriver. + /// + /// [`Event`]: arcana_core::Event fn impl_from(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = @@ -187,6 +191,11 @@ impl Definitions { } } + /// Generates code, that checks uniqueness of [`Event::name()`] and + /// [`Event::ver()`]. + /// + /// [`Event::name()`]: arcana_core::Event::name() + /// [`Event::ver()`]: arcana_core::Event::ver() fn unique_event_name_and_ver(&self) -> TokenStream { if self.attrs.skip_check_unique_name_and_ver() { return TokenStream::new(); From 32f233fa9f9c16891faebd4613b10174232a96ef Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 17 Aug 2021 15:27:39 +0300 Subject: [PATCH 023/104] Cover VersionedEvent derive macro with docs [skip ci] --- codegen/impl/src/event/versioned.rs | 72 +++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index 2ad4c65..baee70f 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -21,16 +21,65 @@ pub(crate) fn derive(input: TokenStream) -> Result { Ok(quote! { #definitions }) } +/// Attributes for [`VersionedEvent`] derive macro. +/// +/// [`VersionedEvent`]: arcana_core::VersionedEvent +#[derive(Default, ParseAttrs)] +struct Attrs { + /// Value for [`VersionedEvent::name()`] impl. + /// + /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() + #[parse(value)] + name: Option, + + /// Value for [`VersionedEvent::ver()`] impl. + /// + /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() + #[parse(value, validate = parses_to_non_zero_u16)] + version: Option, +} + +/// If `val` is [`Some`], checks if it can be parsed to [`NonZeroU16`]. +fn parses_to_non_zero_u16<'a>( + val: impl Into>, +) -> Result<()> { + val.into() + .map(syn::LitInt::base10_parse::) + .transpose() + .map(drop) +} + +/// Definition of [`VersionedEvent`] derive macro. +/// +/// [`VersionedEvent`]: arcana_core::Event #[derive(ToTokens)] #[to_tokens(append(impl_from, unique_event_name_and_ver))] struct Definitions { + /// Struct's [`Ident`]. + /// + /// [`Ident`]: syn::Ident ident: syn::Ident, + + /// Struct's [`Generics`]. + /// + /// [`Generics`]: syn::Generics generics: syn::Generics, + + /// [`Attr::name`] from top-level struct attribute. event_name: syn::LitStr, + + /// [`Attr::version`] from top-level struct attribute. event_ver: syn::LitInt, } impl Definitions { + /// Generates code to derive [`VersionedEvent`] by placing values from + /// [`Attrs`] inside [`VersionedEvent::name()`] and + /// [`VersionedEvent::ver()`] impls. + /// + /// [`VersionedEvent`]: arcana_core::VersionedEvent + /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() + /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() fn impl_from(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = @@ -57,6 +106,11 @@ impl Definitions { } } + /// Generates code, that is used to check uniqueness of [`Event::name()`] + /// and [`Event::ver()`]. + /// + /// [`Event::name()`]: arcana_core::Event::name() + /// [`Event::ver()`]: arcana_core::Event::ver() fn unique_event_name_and_ver(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = @@ -105,24 +159,6 @@ impl TryFrom for Definitions { } } -#[derive(Default, ParseAttrs)] -struct Attrs { - #[parse(value)] - name: Option, - - #[parse(value, validate = parses_to_non_zero_u16)] - version: Option, -} - -fn parses_to_non_zero_u16<'a>( - val: impl Into>, -) -> Result<()> { - val.into() - .map(syn::LitInt::base10_parse::) - .transpose() - .map(drop) -} - #[cfg(test)] mod spec { use super::{derive, quote}; From 18a0c1a32b315a8a55358e800cbbdf9fdfe782b0 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 18 Aug 2021 11:40:43 +0300 Subject: [PATCH 024/104] Move events uniqueness check to arcana_codegen crate [skip ci] --- Cargo.toml | 2 - codegen/Cargo.toml | 1 + codegen/impl/src/event/mod.rs | 12 +-- codegen/impl/src/event/versioned.rs | 4 +- codegen/src/lib.rs | 4 + codegen/src/unique_events.rs | 137 ++++++++++++++++++++++++++++ core/src/es/mod.rs | 2 +- src/codegen.rs | 6 ++ src/lib.rs | 4 +- src/private.rs | 135 --------------------------- 10 files changed, 159 insertions(+), 148 deletions(-) create mode 100644 codegen/src/unique_events.rs create mode 100644 src/codegen.rs delete mode 100644 src/private.rs diff --git a/Cargo.toml b/Cargo.toml index dcbafdf..1f5d69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,6 @@ es = ["arcana-core/es"] [dependencies] arcana-core = { version = "0.1.0", path = "./core" } arcana-codegen = { version = "0.1.0", path = "./codegen" } -static_assertions = "1.1" -once_cell = "1.8" [workspace] members = ["codegen", "codegen/impl", "core"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index ace69e3..563bd03 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -5,3 +5,4 @@ edition = "2018" [dependencies] arcana-codegen-impl = { version = "0.1.0", path = "./impl"} +static_assertions = "1.1" diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 65ff75e..467cce9 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -218,12 +218,12 @@ impl Definitions { quote! { impl #impl_generics #name #ty_generics #where_clause { - ::arcana::unique_event_name_and_ver_for_enum!( + ::arcana::codegen::unique_event_name_and_ver_for_enum!( #max, #event_variants ); } - ::arcana::unique_event_name_and_ver_check!(#name); + ::arcana::codegen::unique_event_name_and_ver_check!(#name); } } } @@ -313,12 +313,12 @@ mod spec { } impl Event { - ::arcana::unique_event_name_and_ver_for_enum!( + ::arcana::codegen::unique_event_name_and_ver_for_enum!( 100000usize, EventUnnamend, EventNamed, ); } - ::arcana::unique_event_name_and_ver_check!(Event); + ::arcana::codegen::unique_event_name_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); @@ -409,12 +409,12 @@ mod spec { } impl Event { - ::arcana::unique_event_name_and_ver_for_enum!( + ::arcana::codegen::unique_event_name_and_ver_for_enum!( 100000usize, EventNamed, ); } - ::arcana::unique_event_name_and_ver_check!(Event); + ::arcana::codegen::unique_event_name_and_ver_check!(Event); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index baee70f..d978bf8 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -120,7 +120,7 @@ impl Definitions { quote! { impl #impl_generics #name #ty_generics #where_clause { - ::arcana::unique_event_name_and_ver_for_struct!( + ::arcana::codegen::unique_event_name_and_ver_for_struct!( #max, #event_name, #event_ver ); } @@ -187,7 +187,7 @@ mod spec { } impl Event { - ::arcana::unique_event_name_and_ver_for_struct!( + ::arcana::codegen::unique_event_name_and_ver_for_struct!( 100000usize, "event", 1 ); } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 21dc255..6633724 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -20,5 +20,9 @@ unused_results )] +pub mod unique_events; + +pub use static_assertions as sa; + #[doc(inline)] pub use arcana_codegen_impl::{Event, VersionedEvent}; diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs new file mode 100644 index 0000000..6511e7b --- /dev/null +++ b/codegen/src/unique_events.rs @@ -0,0 +1,137 @@ +//! Utils for ensuring that every [`Event`] variant has a unique combination of +//! [`Event::name()`] and [`Event::ver()`]. +//! +//! # Explanation +//! +//! Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates +//! `const fn __arcana_events() -> [Option<(&'static str, u16)>; size]` +//! method. Size of outputted array determines max count of unique +//! [`VersionedEvent`]s inside [`Event`] and is tweakable inside +//! `arcana_codegen_impl` crate (default is `100_000` which should be plenty). +//! As these arrays are used only at compile-time, there should be no +//! performance impact at runtime. +//! +//! - Structs +//! +//! [`unique_event_name_and_ver_for_struct`] macro generates function, which +//! returns array with only first occupied entry. The rest of them are +//! [`None`]. +//! +//! - Enums +//! +//! [`unique_event_name_and_ver_for_enum`] macro generates function, which +//! glues subtypes arrays into single continues array. First `n` entries are +//! occupied, while the rest of them are [`None`], where `n` is the number of +//! [`VersionedEvent`]s. As structs deriving [`VersionedEvent`] and enums +//! deriving [`Event`] have the same output by `__arcana_events()` method, +//! top-level enum variants can have different levels of nesting. +//! +//! [`unique_event_name_and_ver_check`] macro generates [`const_assert`] +//! check, which fails in case of duplicated [`Event::name()`] and +//! [`Event::ver()`]. +//! +//! +//! [`const_assert`]: static_assertions::const_assert +//! [`Event`]: trait@crate::Event +//! [`Event::name()`]: trait@crate::Event::name() +//! [`Event::ver()`]: trait@crate::Event::ver() +//! [`VersionedEvent`]: trait@crate::VersionedEvent + +/// Generates `const fn __arcana_events()` method for struct which returns array +/// with only first occupied entry. +#[macro_export] +macro_rules! unique_event_name_and_ver_for_struct { + ($max_events:literal, $event_name:literal, $event_ver:literal) => { + #[allow(clippy::large_stack_arrays)] + pub const fn __arcana_events( + ) -> [Option<(&'static str, u16)>; $max_events] { + let mut res = [None; $max_events]; + res[0] = Some(($event_name, $event_ver)); + res + } + }; +} + +/// Glues `const fn __arcana_events()` methods of all enum variants into single +/// continues array. +#[macro_export] +macro_rules! unique_event_name_and_ver_for_enum { + ($max_events: literal, $($event_name: ty),* $(,)?) => { + #[allow(clippy::large_stack_arrays)] + pub const fn __arcana_events() -> + [Option<(&'static str, u16)>; $max_events] + { + let mut res = [None; $max_events]; + + let mut global = 0; + + $({ + let ev = <$event_name>::__arcana_events(); + let mut local = 0; + while let Some(s) = ev[local] { + res[global] = Some(s); + local += 1; + global += 1; + } + })* + + res + } + }; + } + +/// Checks if `const fn __arcana_events()` has no duplicates. +#[macro_export] +macro_rules! unique_event_name_and_ver_check { + ($event:ty) => { + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + <$event>::__arcana_events() + ) + ); + }; +} + +/// Checks if array has [`Some`] duplicates. +#[must_use] +pub const fn has_duplicates( + events: [Option<(&str, u16)>; N], +) -> bool { + let mut outer = 0; + while let Some((outer_name, outer_ver)) = events[outer] { + let mut inner = outer + 1; + while let Some((inner_name, inner_ver)) = events[inner] { + if str_eq(inner_name, outer_name) && inner_ver == outer_ver { + return true; + } + inner += 1; + } + outer += 1; + } + + false +} + +/// Compares strings constantly. +/// +/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to +/// write custom comparison function. +/// +/// [`Eq`]: std::cmp::Eq +#[must_use] +const fn str_eq(l: &str, r: &str) -> bool { + if l.len() != r.len() { + return false; + } + + let (l, r) = (l.as_bytes(), r.as_bytes()); + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 9d8a1ac..b466d7b 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -78,7 +78,7 @@ impl Version { } } -impl Event for Ev { +impl Event for Ev { fn name(&self) -> Name { ::name() } diff --git a/src/codegen.rs b/src/codegen.rs new file mode 100644 index 0000000..b779279 --- /dev/null +++ b/src/codegen.rs @@ -0,0 +1,6 @@ +//! Re-exports of [`arcana_codegen`]. + +pub use arcana_codegen::{ + sa, unique_event_name_and_ver_check, unique_event_name_and_ver_for_enum, + unique_event_name_and_ver_for_struct, unique_events, +}; diff --git a/src/lib.rs b/src/lib.rs index b2db5b6..e435f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,8 +21,8 @@ )] #[doc(hidden)] -#[cfg(feature = "es")] -pub mod private; +#[cfg(all(feature = "derive", feature = "es"))] +pub mod codegen; #[doc(inline)] #[cfg(feature = "es")] diff --git a/src/private.rs b/src/private.rs deleted file mode 100644 index 91c424c..0000000 --- a/src/private.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Module for utils used in proc macro expansion. - -pub use static_assertions as sa; - -/// Utils for ensuring that every [`Event`] variant has a unique combination of -/// [`Event::name()`] and [`Event::ver()`]. -/// -/// # Explanation -/// -/// Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates -/// `const fn __arcana_events() -> [Option<(&'static str, u16)>; size]` -/// const function. Size of outputted array determines max count of unique -/// [`VersionedEvent`]s inside [`Event`] and is tweakable inside -/// `arcana_codegen_impl` crate (default is `100_000` which should be plenty). -/// As these arrays are used only at compile-time, there should be no -/// performance impact at runtime. -/// -/// - Structs -/// -/// [`unique_event_name_and_ver_for_struct`] macro generates function, which -/// returns array with only first occupied entry. The rest of them are -/// [`None`]. -/// -/// - Enums -/// -/// [`unique_event_name_and_ver_for_enum`] macro generates function, which -/// glues subtypes arrays into single continues array. First `n` entries are -/// occupied, while the rest of them are [`None`], where `n` is the number of -/// [`VersionedEvent`]s. As structs deriving [`VersionedEvent`] and enums -/// deriving [`Event`] have the same output by `__arcana_events()` const -/// function, top-level enum variants can have different levels of nesting. -/// -/// [`unique_event_name_and_ver_check`] macro generates [`const_assert`] -/// check, which fails in case of duplicated [`Event::name()`] and -/// [`Event::ver()`]. -/// -/// -/// [`const_assert`]: static_assertions::const_assert -/// [`Event`]: trait@crate::Event -/// [`Event::name()`]: trait@crate::Event::name() -/// [`Event::ver()`]: trait@crate::Event::ver() -/// [`VersionedEvent`]: trait@crate::VersionedEvent -pub mod unique_event_name_and_ver { - #[doc(hidden)] - #[macro_export] - macro_rules! unique_event_name_and_ver_for_struct { - ($max_events:literal, $event_name:literal, $event_ver:literal) => { - #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_events( - ) -> [Option<(&'static str, u16)>; $max_events] { - let mut res = [None; $max_events]; - res[0] = Some(($event_name, $event_ver)); - res - } - }; - } - - #[doc(hidden)] - #[macro_export] - macro_rules! unique_event_name_and_ver_for_enum { - ($max_events: literal, $($event_name: ty),* $(,)?) => { - #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_events() -> - [Option<(&'static str, u16)>; $max_events] - { - let mut res = [None; $max_events]; - - let mut global = 0; - - $({ - let ev = <$event_name>::__arcana_events(); - let mut local = 0; - while let Some(s) = ev[local] { - res[global] = Some(s); - local += 1; - global += 1; - } - })* - - res - } - }; - } - - #[doc(hidden)] - #[macro_export] - macro_rules! unique_event_name_and_ver_check { - ($event:ty) => { - $crate::private::sa::const_assert!( - $crate::private::unique_event_name_and_ver::all_unique( - <$event>::__arcana_events() - ) - ); - }; - } - - #[doc(hidden)] - #[must_use] - pub const fn all_unique( - events: [Option<(&str, u16)>; N], - ) -> bool { - let mut outer = 0; - while let Some((outer_name, outer_ver)) = events[outer] { - let mut inner = outer + 1; - while let Some((inner_name, inner_ver)) = events[inner] { - if str_eq(inner_name, outer_name) && inner_ver == outer_ver { - return false; - } - inner += 1; - } - outer += 1; - } - - true - } - - #[doc(hidden)] - #[must_use] - const fn str_eq(l: &str, r: &str) -> bool { - if l.len() != r.len() { - return false; - } - - let (l, r) = (l.as_bytes(), r.as_bytes()); - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true - } -} From de5edea7d051f079f096529e48aa0b6585080dd1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 20 Aug 2021 15:07:36 +0300 Subject: [PATCH 025/104] Use only required number of events in uniqueness check [skip ci] --- codegen/impl/src/event/mod.rs | 145 ++++++++++++++++++++++++---- codegen/impl/src/event/versioned.rs | 28 ++++-- codegen/src/unique_events.rs | 65 +------------ src/codegen.rs | 10 +- src/lib.rs | 2 + 5 files changed, 159 insertions(+), 91 deletions(-) diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 467cce9..4a2da26 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -15,8 +15,6 @@ use syn::{ }; use synthez::{ParseAttrs, ToTokens}; -const MAX_UNIQUE_EVENTS: usize = 100_000; - /// Derives [`Event`] for enum. /// /// [`Event`]: arcana_core::Event @@ -201,29 +199,71 @@ impl Definitions { return TokenStream::new(); } - let max = MAX_UNIQUE_EVENTS; let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let event_variants = self + let (event_sizes, event_array_population): ( + Vec, + TokenStream, + ) = self .variants .iter() .filter_map(|(variant, attr)| { (!attr.skip_check_unique_name_and_ver()).then(|| { let ty = &variant.fields.iter().next().unwrap().ty; - quote! { #ty, } + ( + quote! { <#ty as ::arcana::UniqueArcanaEvent>::SIZE }, + quote! {{ + let ev = #ty::__arcana_events(); + let mut local = 0; + while local < ev.len() { + res[global] = ev[local]; + local += 1; + global += 1; + } + }}, + ) }) }) - .collect::(); + .unzip(); + + let event_sizes = event_sizes + .into_iter() + .fold(None, |acc, size| { + Some(acc.map(|acc| quote! { #acc + #size }).unwrap_or(size)) + }) + .unwrap_or(quote! { 1 }); quote! { + #[automatically_derived] + impl #impl_generics ::arcana::UniqueArcanaEvent for + #name #ty_generics #where_clause + { + const SIZE: usize = #event_sizes; + } + impl #impl_generics #name #ty_generics #where_clause { - ::arcana::codegen::unique_event_name_and_ver_for_enum!( - #max, #event_variants - ); + #[automatically_derived] + pub const fn __arcana_events() -> [ + (&'static str, u16); + ::SIZE + ] { + let mut res = + [("", 0); ::SIZE]; + + let mut global = 0; + + #event_array_population + + res + } } - ::arcana::codegen::unique_event_name_and_ver_check!(#name); + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + #name::__arcana_events() + ) + ); } } } @@ -312,13 +352,53 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::UniqueArcanaEvent for Event { + const SIZE: usize = + ::SIZE + + ::SIZE; + } + impl Event { - ::arcana::codegen::unique_event_name_and_ver_for_enum!( - 100000usize, EventUnnamend, EventNamed, - ); + #[automatically_derived] + pub const fn __arcana_events() -> [ + (&'static str, u16); + ::SIZE + ] { + let mut res = + [("", 0); ::SIZE]; + + let mut global = 0; + + { + let ev = EventUnnamend::__arcana_events(); + let mut local = 0; + while local < ev.len() { + res[global] = ev[local]; + local += 1; + global += 1; + } + } + + { + let ev = EventNamed::__arcana_events(); + let mut local = 0; + while local < ev.len() { + res[global] = ev[local]; + local += 1; + global += 1; + } + } + + res + } } - ::arcana::codegen::unique_event_name_and_ver_check!(Event); + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + Event::__arcana_events() + ) + ); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); @@ -408,13 +488,42 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::UniqueArcanaEvent for Event { + const SIZE: usize = + ::SIZE; + } + impl Event { - ::arcana::codegen::unique_event_name_and_ver_for_enum!( - 100000usize, EventNamed, - ); + #[automatically_derived] + pub const fn __arcana_events() -> [ + (&'static str, u16); + ::SIZE + ] { + let mut res = + [("", 0); ::SIZE]; + + let mut global = 0; + + { + let ev = EventNamed::__arcana_events(); + let mut local = 0; + while local < ev.len() { + res[global] = ev[local]; + local += 1; + global += 1; + } + } + + res + } } - ::arcana::codegen::unique_event_name_and_ver_check!(Event); + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + Event::__arcana_events() + ) + ); }; assert_eq!(derive(input).unwrap().to_string(), output.to_string()); diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index d978bf8..a0aa571 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -9,8 +9,6 @@ use quote::quote; use syn::{spanned::Spanned as _, Result}; use synthez::{ParseAttrs, ToTokens}; -use super::MAX_UNIQUE_EVENTS; - /// Derives [`VersionedEvent`] for struct. /// /// [`VersionedEvent`]: arcana_core::VersionedEvent @@ -116,13 +114,19 @@ impl Definitions { let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let (event_name, event_ver) = (&self.event_name, &self.event_ver); - let max = MAX_UNIQUE_EVENTS; quote! { + #[automatically_derived] + impl #impl_generics ::arcana::UniqueArcanaEvent for + #name #ty_generics #where_clause { + const SIZE: usize = 1; + } + impl #impl_generics #name #ty_generics #where_clause { - ::arcana::codegen::unique_event_name_and_ver_for_struct!( - #max, #event_name, #event_ver - ); + #[automatically_derived] + pub const fn __arcana_events() -> [(&'static str, u16); 1] { + [(#event_name, #event_ver)] + } } } } @@ -186,10 +190,16 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::UniqueArcanaEvent for Event { + const SIZE: usize = 1; + } + impl Event { - ::arcana::codegen::unique_event_name_and_ver_for_struct!( - 100000usize, "event", 1 - ); + #[automatically_derived] + pub const fn __arcana_events() -> [(&'static str, u16); 1] { + [("event", 1)] + } } }; diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index 6511e7b..e107a6b 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -37,70 +37,15 @@ //! [`Event::ver()`]: trait@crate::Event::ver() //! [`VersionedEvent`]: trait@crate::VersionedEvent -/// Generates `const fn __arcana_events()` method for struct which returns array -/// with only first occupied entry. -#[macro_export] -macro_rules! unique_event_name_and_ver_for_struct { - ($max_events:literal, $event_name:literal, $event_ver:literal) => { - #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_events( - ) -> [Option<(&'static str, u16)>; $max_events] { - let mut res = [None; $max_events]; - res[0] = Some(($event_name, $event_ver)); - res - } - }; -} - -/// Glues `const fn __arcana_events()` methods of all enum variants into single -/// continues array. -#[macro_export] -macro_rules! unique_event_name_and_ver_for_enum { - ($max_events: literal, $($event_name: ty),* $(,)?) => { - #[allow(clippy::large_stack_arrays)] - pub const fn __arcana_events() -> - [Option<(&'static str, u16)>; $max_events] - { - let mut res = [None; $max_events]; - - let mut global = 0; - - $({ - let ev = <$event_name>::__arcana_events(); - let mut local = 0; - while let Some(s) = ev[local] { - res[global] = Some(s); - local += 1; - global += 1; - } - })* - - res - } - }; - } - -/// Checks if `const fn __arcana_events()` has no duplicates. -#[macro_export] -macro_rules! unique_event_name_and_ver_check { - ($event:ty) => { - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - <$event>::__arcana_events() - ) - ); - }; -} - /// Checks if array has [`Some`] duplicates. #[must_use] -pub const fn has_duplicates( - events: [Option<(&str, u16)>; N], -) -> bool { +pub const fn has_duplicates(events: [(&str, u16); N]) -> bool { let mut outer = 0; - while let Some((outer_name, outer_ver)) = events[outer] { + while outer < events.len() { let mut inner = outer + 1; - while let Some((inner_name, inner_ver)) = events[inner] { + while inner < events.len() { + let (inner_name, inner_ver) = events[inner]; + let (outer_name, outer_ver) = events[outer]; if str_eq(inner_name, outer_name) && inner_ver == outer_ver { return true; } diff --git a/src/codegen.rs b/src/codegen.rs index b779279..9ef7345 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,6 +1,8 @@ //! Re-exports of [`arcana_codegen`]. -pub use arcana_codegen::{ - sa, unique_event_name_and_ver_check, unique_event_name_and_ver_for_enum, - unique_event_name_and_ver_for_struct, unique_events, -}; +pub use arcana_codegen::{sa, unique_events}; + +/// TODO: +pub trait UniqueArcanaEvent { + const SIZE: usize; +} diff --git a/src/lib.rs b/src/lib.rs index e435f2a..46db255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,8 @@ #[cfg(all(feature = "derive", feature = "es"))] pub mod codegen; +pub use codegen::UniqueArcanaEvent; + #[doc(inline)] #[cfg(feature = "es")] pub use arcana_core::{ From fb0260a1de599e9eabc58c2e4f0002577b43d9d0 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 20 Aug 2021 16:13:39 +0300 Subject: [PATCH 026/104] Add arcana-codegen-shim [skip ci] --- .github/workflows/ci.yml | 1 + Cargo.toml | 2 +- codegen/Cargo.toml | 5 ++- codegen/impl/Cargo.toml | 7 ++-- codegen/impl/src/event/mod.rs | 65 ++++++++++++++++++----------- codegen/impl/src/event/versioned.rs | 19 +++++---- codegen/impl/src/lib.rs | 24 +---------- codegen/shim/Cargo.toml | 11 +++++ codegen/shim/README.md | 2 + codegen/shim/src/lib.rs | 43 +++++++++++++++++++ codegen/src/lib.rs | 2 +- codegen/src/unique_events.rs | 19 ++++++--- src/codegen.rs | 10 ++--- src/lib.rs | 2 - 14 files changed, 137 insertions(+), 75 deletions(-) create mode 100644 codegen/shim/Cargo.toml create mode 100644 codegen/shim/README.md create mode 100644 codegen/shim/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8d833a..5426e2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,7 @@ jobs: - arcana - arcana-core - arcana-codegen + - arcana-codegen-shim - arcana-codegen-impl os: - ubuntu diff --git a/Cargo.toml b/Cargo.toml index 1f5d69c..36f013b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ arcana-core = { version = "0.1.0", path = "./core" } arcana-codegen = { version = "0.1.0", path = "./codegen" } [workspace] -members = ["codegen", "codegen/impl", "core"] +members = ["codegen", "codegen/impl", "codegen/shim", "core"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 563bd03..3a523f7 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -4,5 +4,8 @@ version = "0.1.0" edition = "2018" [dependencies] -arcana-codegen-impl = { version = "0.1.0", path = "./impl"} +arcana-codegen-shim = { version = "0.1.0", path = "./shim"} static_assertions = "1.1" + +[dev-dependencies] +arcana-core = { path = "../core" } diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 608fecd..90e065b 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -3,13 +3,12 @@ name = "arcana-codegen-impl" version = "0.1.0" edition = "2018" -[lib] -proc-macro = true - [dependencies] -arcana-core = { version = "0.1.0", path = "../../core" } proc-macro2 = "1.0" quote = "1.0" strum = { version = "0.21", features = ["derive"] } syn = "1.0" synthez = "0.1" + +[dev-dependencies] +arcana-core = { version = "0.1.0", path = "../../core" } diff --git a/codegen/impl/src/event/mod.rs b/codegen/impl/src/event/mod.rs index 4a2da26..c461b16 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/impl/src/event/mod.rs @@ -2,7 +2,7 @@ //! //! [`Event`]: arcana_core::Event -pub(crate) mod versioned; +pub mod versioned; use std::{convert::TryFrom, str::FromStr as _}; @@ -18,7 +18,13 @@ use synthez::{ParseAttrs, ToTokens}; /// Derives [`Event`] for enum. /// /// [`Event`]: arcana_core::Event -pub(crate) fn derive(input: TokenStream) -> syn::Result { +/// +/// # Errors +/// +/// - If `input` isn't an `enum`; +/// - If `enum` variant consist not from single event; +/// - If failed to parse [`Attrs`]. +pub fn derive(input: TokenStream) -> syn::Result { let input: syn::DeriveInput = syn::parse2(input)?; let definitions = Definitions::try_from(input)?; @@ -28,8 +34,8 @@ pub(crate) fn derive(input: TokenStream) -> syn::Result { /// Attributes for [`Event`] derive macro. /// /// [`Event`]: arcana_core::Event -#[derive(Default, ParseAttrs)] -struct Attrs { +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { /// `#[event(skip(...))` attribute. #[parse(value)] skip: Option>, @@ -41,7 +47,8 @@ impl Attrs { /// /// [`Event::name()`]: arcana_core::Event::name() /// [`Event::ver()`]: arcana_core::Event::ver() - fn skip_check_unique_name_and_ver(&self) -> bool { + #[must_use] + pub fn skip_check_unique_name_and_ver(&self) -> bool { matches!( self.skip.as_ref().map(|sp| sp.item), Some(SkipAttr::CheckUniqueNameAndVer), @@ -68,7 +75,7 @@ impl Spanned for Spanning { /// Inner value for `#[event(skip(...))]` attribute. #[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] #[strum(serialize_all = "snake_case")] -enum SkipAttr { +pub enum SkipAttr { /// Variant for skipping uniqueness check of [`Event::name()`] and /// [`Event::ver()`]. /// @@ -212,7 +219,9 @@ impl Definitions { (!attr.skip_check_unique_name_and_ver()).then(|| { let ty = &variant.fields.iter().next().unwrap().ty; ( - quote! { <#ty as ::arcana::UniqueArcanaEvent>::SIZE }, + quote! { + <#ty as ::arcana::codegen::UniqueEvents>::COUNT + }, quote! {{ let ev = #ty::__arcana_events(); let mut local = 0; @@ -236,20 +245,22 @@ impl Definitions { quote! { #[automatically_derived] - impl #impl_generics ::arcana::UniqueArcanaEvent for + impl #impl_generics ::arcana::codegen::UniqueEvents for #name #ty_generics #where_clause { - const SIZE: usize = #event_sizes; + const COUNT: usize = #event_sizes; } impl #impl_generics #name #ty_generics #where_clause { #[automatically_derived] pub const fn __arcana_events() -> [ (&'static str, u16); - ::SIZE + ::COUNT ] { - let mut res = - [("", 0); ::SIZE]; + let mut res =[ + ("", 0); + ::COUNT + ]; let mut global = 0; @@ -353,20 +364,22 @@ mod spec { } #[automatically_derived] - impl ::arcana::UniqueArcanaEvent for Event { - const SIZE: usize = - ::SIZE + - ::SIZE; + impl ::arcana::codegen::UniqueEvents for Event { + const COUNT: usize = + ::COUNT + + ::COUNT; } impl Event { #[automatically_derived] pub const fn __arcana_events() -> [ (&'static str, u16); - ::SIZE + ::COUNT ] { - let mut res = - [("", 0); ::SIZE]; + let mut res = [ + ("", 0); + ::COUNT + ]; let mut global = 0; @@ -489,19 +502,21 @@ mod spec { } #[automatically_derived] - impl ::arcana::UniqueArcanaEvent for Event { - const SIZE: usize = - ::SIZE; + impl ::arcana::codegen::UniqueEvents for Event { + const COUNT: usize = + ::COUNT; } impl Event { #[automatically_derived] pub const fn __arcana_events() -> [ (&'static str, u16); - ::SIZE + ::COUNT ] { - let mut res = - [("", 0); ::SIZE]; + let mut res = [ + ("", 0); + ::COUNT + ]; let mut global = 0; diff --git a/codegen/impl/src/event/versioned.rs b/codegen/impl/src/event/versioned.rs index a0aa571..0778cfe 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/impl/src/event/versioned.rs @@ -12,7 +12,12 @@ use synthez::{ParseAttrs, ToTokens}; /// Derives [`VersionedEvent`] for struct. /// /// [`VersionedEvent`]: arcana_core::VersionedEvent -pub(crate) fn derive(input: TokenStream) -> Result { +/// +/// # Errors +/// +/// - If `input` isn't a `struct`; +/// - If failed to parse [`Attrs`]. +pub fn derive(input: TokenStream) -> Result { let input = syn::parse2::(input)?; let definitions = Definitions::try_from(input)?; @@ -22,8 +27,8 @@ pub(crate) fn derive(input: TokenStream) -> Result { /// Attributes for [`VersionedEvent`] derive macro. /// /// [`VersionedEvent`]: arcana_core::VersionedEvent -#[derive(Default, ParseAttrs)] -struct Attrs { +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { /// Value for [`VersionedEvent::name()`] impl. /// /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() @@ -117,9 +122,9 @@ impl Definitions { quote! { #[automatically_derived] - impl #impl_generics ::arcana::UniqueArcanaEvent for + impl #impl_generics ::arcana::codegen::UniqueEvents for #name #ty_generics #where_clause { - const SIZE: usize = 1; + const COUNT: usize = 1; } impl #impl_generics #name #ty_generics #where_clause { @@ -191,8 +196,8 @@ mod spec { } #[automatically_derived] - impl ::arcana::UniqueArcanaEvent for Event { - const SIZE: usize = 1; + impl ::arcana::codegen::UniqueEvents for Event { + const COUNT: usize = 1; } impl Event { diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index 018e182..cee4505 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -20,26 +20,4 @@ unused_results )] -mod event; - -use proc_macro::TokenStream; - -/// Macro for deriving [`Event`]. -/// -/// [`Event`]: arcana_core::Event -#[proc_macro_derive(Event, attributes(event))] -pub fn derive_event(input: TokenStream) -> TokenStream { - event::derive(input.into()) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -/// Macro for deriving [`VersionedEvent`]. -/// -/// [`VersionedEvent`]: arcana_core::VersionedEvent -#[proc_macro_derive(VersionedEvent, attributes(event))] -pub fn derive_versioned_event(input: TokenStream) -> TokenStream { - event::versioned::derive(input.into()) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} +pub mod event; diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml new file mode 100644 index 0000000..4047884 --- /dev/null +++ b/codegen/shim/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arcana-codegen-shim" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +arcana-codegen-impl = { path = "../impl" } +syn = "1.0" diff --git a/codegen/shim/README.md b/codegen/shim/README.md new file mode 100644 index 0000000..b4c8e23 --- /dev/null +++ b/codegen/shim/README.md @@ -0,0 +1,2 @@ +arcana-codegen-shim +=================== diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs new file mode 100644 index 0000000..a7a8568 --- /dev/null +++ b/codegen/shim/src/lib.rs @@ -0,0 +1,43 @@ +#![doc = include_str!("../README.md")] +#![deny( + nonstandard_style, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + trivial_casts, + trivial_numeric_casts +)] +#![forbid(non_ascii_idents, unsafe_code)] +#![warn( + deprecated_in_future, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + unused_import_braces, + unused_labels, + unused_qualifications, + unused_results +)] + +use proc_macro::TokenStream; + +/// Macro for deriving [`Event`]. +/// +/// [`Event`]: arcana_core::Event +#[proc_macro_derive(Event, attributes(event))] +pub fn derive_event(input: TokenStream) -> TokenStream { + arcana_codegen_impl::event::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// Macro for deriving [`VersionedEvent`]. +/// +/// [`VersionedEvent`]: arcana_core::VersionedEvent +#[proc_macro_derive(VersionedEvent, attributes(event))] +pub fn derive_versioned_event(input: TokenStream) -> TokenStream { + arcana_codegen_impl::event::versioned::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 6633724..261f015 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -25,4 +25,4 @@ pub mod unique_events; pub use static_assertions as sa; #[doc(inline)] -pub use arcana_codegen_impl::{Event, VersionedEvent}; +pub use arcana_codegen_shim::{Event, VersionedEvent}; diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index e107a6b..d1bbc09 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -32,12 +32,21 @@ //! //! //! [`const_assert`]: static_assertions::const_assert -//! [`Event`]: trait@crate::Event -//! [`Event::name()`]: trait@crate::Event::name() -//! [`Event::ver()`]: trait@crate::Event::ver() -//! [`VersionedEvent`]: trait@crate::VersionedEvent +//! [`Event`]: arcana_core::Event +//! [`Event::name()`]: arcana_core::Event::name() +//! [`Event::ver()`]: arcana_core::Event::ver() +//! [`VersionedEvent`]: arcana_core::VersionedEvent -/// Checks if array has [`Some`] duplicates. +/// Trait for keeping track of number of [`VersionedEvent`]s. +pub trait UniqueEvents { + /// Number of [`VersionedEvent`]s in this [`Event`]. + /// + /// [`Event`]: arcana_core::Event + /// [`VersionedEvent`]: arcana_core::VersionedEvent + const COUNT: usize; +} + +/// Checks if array has duplicates. #[must_use] pub const fn has_duplicates(events: [(&str, u16); N]) -> bool { let mut outer = 0; diff --git a/src/codegen.rs b/src/codegen.rs index 9ef7345..e77ff32 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,8 +1,6 @@ //! Re-exports of [`arcana_codegen`]. -pub use arcana_codegen::{sa, unique_events}; - -/// TODO: -pub trait UniqueArcanaEvent { - const SIZE: usize; -} +pub use arcana_codegen::{ + sa, + unique_events::{self, UniqueEvents}, +}; diff --git a/src/lib.rs b/src/lib.rs index 46db255..e435f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,6 @@ #[cfg(all(feature = "derive", feature = "es"))] pub mod codegen; -pub use codegen::UniqueArcanaEvent; - #[doc(inline)] #[cfg(feature = "es")] pub use arcana_core::{ From d74ae8e1cf1871575cf2b0e3fa2ad10d97f9edb7 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 23 Aug 2021 09:32:11 +0300 Subject: [PATCH 027/104] Add MSRV check --- .github/workflows/ci.yml | 41 ++++++++++++++----- Cargo.toml | 8 ++-- codegen/Cargo.toml | 8 ++-- codegen/core/Cargo.toml | 20 +++++++++ codegen/{impl => core}/README.md | 0 codegen/{impl => core}/src/event/mod.rs | 16 +++++--- codegen/{impl => core}/src/event/versioned.rs | 12 +++--- codegen/{impl => core}/src/lib.rs | 0 codegen/impl/Cargo.toml | 14 ------- codegen/shim/Cargo.toml | 9 ++-- codegen/shim/src/lib.rs | 4 +- codegen/src/unique_events.rs | 33 ++++----------- core/Cargo.toml | 2 +- src/lib.rs | 2 +- 14 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 codegen/core/Cargo.toml rename codegen/{impl => core}/README.md (100%) rename codegen/{impl => core}/src/event/mod.rs (97%) rename codegen/{impl => core}/src/event/versioned.rs (95%) rename codegen/{impl => core}/src/lib.rs (100%) delete mode 100644 codegen/impl/Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5426e2a..20ada9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,36 @@ jobs: - run: make test crate=${{ matrix.crate }} + msrv: + name: MSRV + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/v') + || !contains(github.event.head_commit.message, '[skip ci]') }} + strategy: + fail-fast: false + matrix: + msrv: [ '1.54.0' ] + os: + - ubuntu + - macOS + - windows + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.msrv }} + override: true + + - run: cargo +nightly update -Z minimal-versions + + - run: make test + @@ -96,14 +126,3 @@ jobs: override: true - run: make cargo.doc open=no - - - name: Finalize - run: | - CRATE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]' | cut -f2 -d"/") - echo "" > target/doc/index.html - touch target/doc/.nojekyll - - name: Upload as artifact - uses: actions/upload-artifact@v2 - with: - name: Documentation - path: target/doc diff --git a/Cargo.toml b/Cargo.toml index 36f013b..2510029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcana" -version = "0.1.0" +version = "0.1.0-dev" edition = "2018" [features] @@ -9,8 +9,8 @@ derive = [] es = ["arcana-core/es"] [dependencies] -arcana-core = { version = "0.1.0", path = "./core" } -arcana-codegen = { version = "0.1.0", path = "./codegen" } +arcana-core = { version = "0.1.0-dev", path = "./core" } +arcana-codegen = { version = "0.1.0-dev", path = "./codegen" } [workspace] -members = ["codegen", "codegen/impl", "codegen/shim", "core"] +members = ["codegen", "codegen/core", "codegen/shim", "core"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 3a523f7..1be1361 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,11 +1,9 @@ [package] name = "arcana-codegen" -version = "0.1.0" +version = "0.1.0-dev" edition = "2018" [dependencies] -arcana-codegen-shim = { version = "0.1.0", path = "./shim"} +arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim"} +arcana-core = { version = "0.1.0-dev", path = "../core" } static_assertions = "1.1" - -[dev-dependencies] -arcana-core = { path = "../core" } diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml new file mode 100644 index 0000000..26e2ce8 --- /dev/null +++ b/codegen/core/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "arcana-codegen-core" +version = "0.1.0-dev" +edition = "2018" + +[dependencies] +arcana-core = { version = "0.1.0-dev", path = "../../core" } +strum = { version = "0.21", features = ["derive"] } +[dependencies.proc-macro2] + version = "1.0" + default-features = false +[dependencies.quote] + version = "1.0" + default-features = false +[dependencies.syn] + version = "1.0" + default-features = false +[dependencies.synthez] + version = "0.1" + default-features = false diff --git a/codegen/impl/README.md b/codegen/core/README.md similarity index 100% rename from codegen/impl/README.md rename to codegen/core/README.md diff --git a/codegen/impl/src/event/mod.rs b/codegen/core/src/event/mod.rs similarity index 97% rename from codegen/impl/src/event/mod.rs rename to codegen/core/src/event/mod.rs index c461b16..533fe3d 100644 --- a/codegen/impl/src/event/mod.rs +++ b/codegen/core/src/event/mod.rs @@ -25,7 +25,7 @@ use synthez::{ParseAttrs, ToTokens}; /// - If `enum` variant consist not from single event; /// - If failed to parse [`Attrs`]. pub fn derive(input: TokenStream) -> syn::Result { - let input: syn::DeriveInput = syn::parse2(input)?; + let input = syn::parse2::(input)?; let definitions = Definitions::try_from(input)?; Ok(quote! { #definitions }) @@ -196,11 +196,13 @@ impl Definitions { } } - /// Generates code, that checks uniqueness of [`Event::name()`] and - /// [`Event::ver()`]. + /// Generates functions, that returns array composed from arrays of all enum + /// variants. Resulting array size is count of all [`VersionedEvent`]s. /// - /// [`Event::name()`]: arcana_core::Event::name() - /// [`Event::ver()`]: arcana_core::Event::ver() + /// Checks uniqueness of all [`Event::name`]s and [`Event::ver`]s. + /// + /// [`Event::name`]: arcana_core::Event::name() + /// [`Event::ver`]: arcana_core::Event::ver() fn unique_event_name_and_ver(&self) -> TokenStream { if self.attrs.skip_check_unique_name_and_ver() { return TokenStream::new(); @@ -217,6 +219,8 @@ impl Definitions { .iter() .filter_map(|(variant, attr)| { (!attr.skip_check_unique_name_and_ver()).then(|| { + // Unwrapping is safe here as we checked for `.len() == 1` + // in TryFrom impl. let ty = &variant.fields.iter().next().unwrap().ty; ( quote! { @@ -257,7 +261,7 @@ impl Definitions { (&'static str, u16); ::COUNT ] { - let mut res =[ + let mut res = [ ("", 0); ::COUNT ]; diff --git a/codegen/impl/src/event/versioned.rs b/codegen/core/src/event/versioned.rs similarity index 95% rename from codegen/impl/src/event/versioned.rs rename to codegen/core/src/event/versioned.rs index 0778cfe..1100c41 100644 --- a/codegen/impl/src/event/versioned.rs +++ b/codegen/core/src/event/versioned.rs @@ -109,11 +109,12 @@ impl Definitions { } } - /// Generates code, that is used to check uniqueness of [`Event::name()`] - /// and [`Event::ver()`]. + /// Generates functions, that returns array of size 1 with + /// [`VersionedEvent::name()`] and [`VersionedEvent::ver()`]. Used for + /// uniqueness check. /// - /// [`Event::name()`]: arcana_core::Event::name() - /// [`Event::ver()`]: arcana_core::Event::ver() + /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() + /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() fn unique_event_name_and_ver(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = @@ -123,7 +124,8 @@ impl Definitions { quote! { #[automatically_derived] impl #impl_generics ::arcana::codegen::UniqueEvents for - #name #ty_generics #where_clause { + #name #ty_generics #where_clause + { const COUNT: usize = 1; } diff --git a/codegen/impl/src/lib.rs b/codegen/core/src/lib.rs similarity index 100% rename from codegen/impl/src/lib.rs rename to codegen/core/src/lib.rs diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml deleted file mode 100644 index 90e065b..0000000 --- a/codegen/impl/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "arcana-codegen-impl" -version = "0.1.0" -edition = "2018" - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -strum = { version = "0.21", features = ["derive"] } -syn = "1.0" -synthez = "0.1" - -[dev-dependencies] -arcana-core = { version = "0.1.0", path = "../../core" } diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 4047884..4279fea 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "arcana-codegen-shim" -version = "0.1.0" +version = "0.1.0-dev" edition = "2018" [lib] proc-macro = true [dependencies] -arcana-codegen-impl = { path = "../impl" } -syn = "1.0" +arcana-codegen-core = { version = "0.1.0-dev", path = "../core" } +arcana-core = { version = "0.1.0-dev", path = "../../core" } +[dependencies.syn] + version = "1.0" + default-features = false diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index a7a8568..4e675aa 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -27,7 +27,7 @@ use proc_macro::TokenStream; /// [`Event`]: arcana_core::Event #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { - arcana_codegen_impl::event::derive(input.into()) + arcana_codegen_core::event::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -37,7 +37,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream { /// [`VersionedEvent`]: arcana_core::VersionedEvent #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { - arcana_codegen_impl::event::versioned::derive(input.into()) + arcana_codegen_core::event::versioned::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index d1bbc09..988ca4f 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -4,40 +4,23 @@ //! # Explanation //! //! Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates -//! `const fn __arcana_events() -> [Option<(&'static str, u16)>; size]` -//! method. Size of outputted array determines max count of unique -//! [`VersionedEvent`]s inside [`Event`] and is tweakable inside -//! `arcana_codegen_impl` crate (default is `100_000` which should be plenty). -//! As these arrays are used only at compile-time, there should be no -//! performance impact at runtime. -//! -//! - Structs -//! -//! [`unique_event_name_and_ver_for_struct`] macro generates function, which -//! returns array with only first occupied entry. The rest of them are -//! [`None`]. -//! -//! - Enums -//! -//! [`unique_event_name_and_ver_for_enum`] macro generates function, which -//! glues subtypes arrays into single continues array. First `n` entries are -//! occupied, while the rest of them are [`None`], where `n` is the number of -//! [`VersionedEvent`]s. As structs deriving [`VersionedEvent`] and enums -//! deriving [`Event`] have the same output by `__arcana_events()` method, -//! top-level enum variants can have different levels of nesting. -//! -//! [`unique_event_name_and_ver_check`] macro generates [`const_assert`] -//! check, which fails in case of duplicated [`Event::name()`] and -//! [`Event::ver()`]. +//! `const fn __arcana_events() -> [(&'static str, u16); size]` method. This +//! array consists of [`EventName`]s and [`EventVersion`]s of all +//! [`VersionedEvent`]s. Uniqueness is checked with [`const_assert`] of +//! [`has_duplicates`]. //! //! //! [`const_assert`]: static_assertions::const_assert //! [`Event`]: arcana_core::Event //! [`Event::name()`]: arcana_core::Event::name() //! [`Event::ver()`]: arcana_core::Event::ver() +//! [`EventName`]: arcana_core::EventName +//! [`EventVersion`]: arcana_core::EventVersion //! [`VersionedEvent`]: arcana_core::VersionedEvent /// Trait for keeping track of number of [`VersionedEvent`]s. +/// +/// [`VersionedEvent`]: arcana_core::VersionedEvent pub trait UniqueEvents { /// Number of [`VersionedEvent`]s in this [`Event`]. /// diff --git a/core/Cargo.toml b/core/Cargo.toml index 13acfd0..82e12db 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcana-core" -version = "0.1.0" +version = "0.1.0-dev" edition = "2018" [features] diff --git a/src/lib.rs b/src/lib.rs index e435f2a..5d532bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub use arcana_core::{ /// /// # Examples /// -/// ```compile_fail +/// ```compile_fail,E0080 /// # use arcana::{Event, VersionedEvent}; /// # /// #[derive(VersionedEvent)] From eff3a0aea5c85f8e7e2474299784b5f0f6704cb3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 23 Aug 2021 09:33:18 +0300 Subject: [PATCH 028/104] Fix CI --- .github/workflows/ci.yml | 2 +- codegen/core/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20ada9e..a01b82c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - arcana-core - arcana-codegen - arcana-codegen-shim - - arcana-codegen-impl + - arcana-codegen-core os: - ubuntu - macOS diff --git a/codegen/core/README.md b/codegen/core/README.md index 594ebe3..b409333 100644 --- a/codegen/core/README.md +++ b/codegen/core/README.md @@ -1,2 +1,2 @@ -arcana-codegen-impl +arcana-codegen-core =================== From 373d0c1eb29c42a88ee4ff63475a03669d73a819 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 23 Aug 2021 09:50:15 +0300 Subject: [PATCH 029/104] Remove safety_guard dependency --- core/Cargo.toml | 1 - core/src/es/mod.rs | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 82e12db..f57c1c7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,4 +10,3 @@ es = [] [dependencies] derive_more = "0.99" ref-cast = "1.0" -safety-guard = "0.1" diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index b466d7b..5931441 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -6,7 +6,6 @@ use std::{convert::TryFrom, num::NonZeroU16}; use derive_more::{Display, Into}; use ref_cast::RefCast; -use safety_guard::safety; /// [Event Sourcing] event that describes something that has occurred (happened /// fact). @@ -69,11 +68,14 @@ impl Version { /// Creates a new [`Version`] out of the given `val`ue without checking its /// invariants. + /// + /// # Safety + /// + /// The given `val`ue must not be `0` (zero). #[allow(unsafe_code)] #[inline] #[must_use] - #[safety(ne(val, 0), "The given `val`ue must not be `0` (zero).")] - pub unsafe fn new_unchecked(val: u16) -> Self { + pub const unsafe fn new_unchecked(val: u16) -> Self { Self(NonZeroU16::new_unchecked(val)) } } From 4ad217bd1a119ae13e3f6de47fb60952dd5b9317 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 24 Aug 2021 14:22:06 +0300 Subject: [PATCH 030/104] Some corrections [skip ci] --- .editorconfig | 1 + Cargo.toml | 15 +- codegen/Cargo.toml | 16 +- codegen/README.md | 10 + codegen/core/Cargo.toml | 30 +-- codegen/core/README.md | 13 ++ codegen/core/src/{ => es}/event/mod.rs | 92 ++++---- codegen/core/src/es/event/versioned.rs | 296 ++++++++++++++++++++++++ codegen/core/src/es/mod.rs | 5 + codegen/core/src/event/versioned.rs | 304 ------------------------- codegen/core/src/lib.rs | 2 +- codegen/shim/Cargo.toml | 18 +- codegen/shim/README.md | 13 ++ codegen/shim/src/lib.rs | 11 +- codegen/src/lib.rs | 4 +- codegen/src/unique_events.rs | 40 ++-- core/Cargo.toml | 13 +- core/README.md | 10 + core/src/es/event.rs | 119 ++++++++++ core/src/es/mod.rs | 128 +---------- core/src/lib.rs | 13 +- src/lib.rs | 5 +- 22 files changed, 620 insertions(+), 538 deletions(-) rename codegen/core/src/{ => es}/event/mod.rs (98%) create mode 100644 codegen/core/src/es/event/versioned.rs create mode 100644 codegen/core/src/es/mod.rs delete mode 100644 codegen/core/src/event/versioned.rs create mode 100644 core/src/es/event.rs diff --git a/.editorconfig b/.editorconfig index 5f59438..a7c1985 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,3 +20,4 @@ indent_size = 4 [*.toml] indent_style = space indent_size = 4 +max_line_length = off diff --git a/Cargo.toml b/Cargo.toml index 2510029..0961edb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,15 +2,24 @@ name = "arcana" version = "0.1.0-dev" edition = "2018" +resolver = "2" +description = "Opionated CQRS/ES framework with type magic." +authors = [ + "Ilya Solovyiov ", + "Kai Ren " +] +documentation = "https://docs.rs/arcana-core" +homepage = "https://github.com/arcana-rs/arcana" +repository = "https://github.com/arcana-rs/arcana" +readme = "README.md" [features] -default = ["derive", "es"] -derive = [] +derive = ["arcana-codegen"] es = ["arcana-core/es"] [dependencies] arcana-core = { version = "0.1.0-dev", path = "./core" } -arcana-codegen = { version = "0.1.0-dev", path = "./codegen" } +arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } [workspace] members = ["codegen", "codegen/core", "codegen/shim", "core"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 1be1361..a00ce95 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -2,8 +2,20 @@ name = "arcana-codegen" version = "0.1.0-dev" edition = "2018" +resolver = "2" +description = "Code generation of `arcana` crate." +authors = [ + "Ilya Solovyiov ", + "Kai Ren " +] +documentation = "https://docs.rs/arcana-codegen" +homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen" +repository = "https://github.com/arcana-rs/arcana" +readme = "README.md" [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim"} -arcana-core = { version = "0.1.0-dev", path = "../core" } -static_assertions = "1.1" +static_assertions = { version = "1.1", default-features = false } + +[dev-dependencies] +arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"] } diff --git a/codegen/README.md b/codegen/README.md index 35c404e..07034d6 100644 --- a/codegen/README.md +++ b/codegen/README.md @@ -1,2 +1,12 @@ arcana-codegen ============== + +Code generation for [`arcana`] crate. + +DO NOT use it directly, use [`arcana`] crate instead. + + + + + +[`arcana`]: https://docs.rs/arcana diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml index 26e2ce8..57d2122 100644 --- a/codegen/core/Cargo.toml +++ b/codegen/core/Cargo.toml @@ -2,19 +2,23 @@ name = "arcana-codegen-core" version = "0.1.0-dev" edition = "2018" +resolver = "2" +description = "Core implementations of `arcana-codegen` crate." +authors = [ + "Ilya Solovyiov ", + "Kai Ren " +] +documentation = "https://docs.rs/arcana-codegen-core" +homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen/core" +repository = "https://github.com/arcana-rs/arcana" +readme = "README.md" [dependencies] -arcana-core = { version = "0.1.0-dev", path = "../../core" } +proc-macro2 = { version = "1.0", default-features = false } +quote = { version = "1.0", default-features = false } strum = { version = "0.21", features = ["derive"] } -[dependencies.proc-macro2] - version = "1.0" - default-features = false -[dependencies.quote] - version = "1.0" - default-features = false -[dependencies.syn] - version = "1.0" - default-features = false -[dependencies.synthez] - version = "0.1" - default-features = false +syn = { version = "1.0", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } +synthez = { version = "0.1", default-features = false } + +[dev-dependencies] +arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } diff --git a/codegen/core/README.md b/codegen/core/README.md index b409333..4063a19 100644 --- a/codegen/core/README.md +++ b/codegen/core/README.md @@ -1,2 +1,15 @@ arcana-codegen-core =================== + +Core implementations of [`arcana-codegen`] crate. + +DO NOT use it directly, use [`arcana`] crate instead. Unless you need to reuse [`arcana-codegen`] inner machinery in your own [proc macro][1] crate. + + + + + +[`arcana`]: https://docs.rs/arcana +[`arcana-codegen`]: https://docs.rs/arcana-codegen + +[1]: https://doc.rust-lang.org/reference/procedural-macros.html diff --git a/codegen/core/src/event/mod.rs b/codegen/core/src/es/event/mod.rs similarity index 98% rename from codegen/core/src/event/mod.rs rename to codegen/core/src/es/event/mod.rs index 533fe3d..aa83a8e 100644 --- a/codegen/core/src/event/mod.rs +++ b/codegen/core/src/es/event/mod.rs @@ -1,6 +1,4 @@ -//! Definition of [`Event`] derive macro for enums. -//! -//! [`Event`]: arcana_core::Event +//! `#[derive(Event)]` macro implementation. pub mod versioned; @@ -26,7 +24,7 @@ use synthez::{ParseAttrs, ToTokens}; /// - If failed to parse [`Attrs`]. pub fn derive(input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; - let definitions = Definitions::try_from(input)?; + let definitions = Definition::try_from(input)?; Ok(quote! { #definitions }) } @@ -107,7 +105,7 @@ impl Parse for Spanning { /// [`Event`]: arcana_core::Event #[derive(ToTokens)] #[to_tokens(append(impl_from, unique_event_name_and_ver))] -struct Definitions { +struct Definition { /// Enum's [`Ident`]. /// /// [`Ident`]: syn::Ident @@ -130,7 +128,48 @@ struct Definitions { attrs: Attrs, } -impl Definitions { +impl TryFrom for Definition { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> syn::Result { + let data = if let syn::Data::Enum(data) = &input.data { + data + } else { + return Err(syn::Error::new( + input.span(), + "Expected enum. \ + Consider using arcana::VersionedEvent for structs", + )); + }; + + for variant in &data.variants { + if variant.fields.len() != 1 { + return Err(syn::Error::new( + variant.span(), + "Enum variants must have exactly 1 field", + )); + } + } + + let attrs = Attrs::parse_attrs("event", &input)?; + let variants = data + .variants + .iter() + .map(|variant| { + Ok((variant.clone(), Attrs::parse_attrs("event", variant)?)) + }) + .collect::>()?; + + Ok(Self { + ident: input.ident, + generics: input.generics, + variants, + attrs, + }) + } +} + +impl Definition { /// Generates code to derive [`Event`] by simply matching over every enum /// variant, which is expected to be itself [`Event`] deriver. /// @@ -283,47 +322,6 @@ impl Definitions { } } -impl TryFrom for Definitions { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> syn::Result { - let data = if let syn::Data::Enum(data) = &input.data { - data - } else { - return Err(syn::Error::new( - input.span(), - "Expected enum. \ - Consider using arcana::VersionedEvent for structs", - )); - }; - - for variant in &data.variants { - if variant.fields.len() != 1 { - return Err(syn::Error::new( - variant.span(), - "Enum variants must have exactly 1 field", - )); - } - } - - let attrs = Attrs::parse_attrs("event", &input)?; - let variants = data - .variants - .iter() - .map(|variant| { - Ok((variant.clone(), Attrs::parse_attrs("event", variant)?)) - }) - .collect::>()?; - - Ok(Self { - ident: input.ident, - generics: input.generics, - variants, - attrs, - }) - } -} - #[cfg(test)] mod spec { use super::{derive, quote}; diff --git a/codegen/core/src/es/event/versioned.rs b/codegen/core/src/es/event/versioned.rs new file mode 100644 index 0000000..cc4e3b8 --- /dev/null +++ b/codegen/core/src/es/event/versioned.rs @@ -0,0 +1,296 @@ +//! `#[derive(event::Versioned)]` macro implementation. + +use std::{convert::TryFrom, num::NonZeroU16}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned as _; +use synthez::{ParseAttrs, Required, ToTokens}; + +/// Expands `#[derive(event::Versioned)]` macro. +/// +/// # Errors +/// +/// - If `input` isn't a Rust struct definition; +/// - If failed to parse [`Attrs`]. +pub fn derive(input: TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + let definition = Definition::try_from(input)?; + + Ok(quote! { #definition }) +} + +/// Helper attributes of `#[derive(event::Versioned)]` macro. +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { + /// Value to return by [`event::Versioned::name()`][0] method. + /// + /// [0]: arcana_core::es::event::Versioned::name + #[parse(value)] + pub name: Required, + + /// Value to return by [`event::Versioned::version()`][0] method. + /// + /// [0]: arcana_core::es::event::Versioned::version + #[parse(value, alias = ver, validate = parses_as_non_zero_u16)] + pub version: Required, +} + +/// If `val` is [`Some`], checks if it can be parsed to [`NonZeroU16`]. +fn parses_as_non_zero_u16(val: &Required) -> syn::Result<()> { + syn::LitInt::base10_parse::(&**val).map(drop) +} + +/// Representation of a struct implementing [`event::Versioned`][0], used for +/// code generation. +/// +/// [0]: arcana_core::es::event::Versioned +#[derive(Debug, ToTokens)] +#[to_tokens(append(impl_event_versioned, gen_uniqueness_glue_code))] +pub struct Definition { + /// [`syn::Ident`] of this structure's type. + pub ident: syn::Ident, + + /// [`syn::Generics`] of this structure's type. + pub generics: syn::Generics, + + /// Value to return by [`event::Versioned::name()`][0] method in the + /// generated code. + /// + /// [0]: arcana_core::es::event::Versioned::name + pub event_name: syn::LitStr, + + /// Value to return by [`event::Versioned::version()`][0] method in the + /// generated code. + /// + /// [0]: arcana_core::es::event::Versioned::version + pub event_version: syn::LitInt, +} + +impl TryFrom for Definition { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> syn::Result { + if !matches!(input.data, syn::Data::Struct(..)) { + return Err(syn::Error::new( + input.span(), + "expected struct only, \ + consider using `arcana::es::Event` for enums", + )); + } + + let attrs = Attrs::parse_attrs("event", &input)?; + + Ok(Self { + ident: input.ident, + generics: input.generics, + // TODO: Use `.into_inner()` once available. + event_name: (*attrs.name).clone(), + event_version: (*attrs.version).clone(), + }) + } +} + +impl Definition { + /// Generates code to derive [`event::Versioned`][0] trait. + /// + /// [0]: arcana_core::es::event::Versioned + pub fn impl_event_versioned(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + + let (event_name, event_ver) = (&self.event_name, &self.event_version); + + quote! { + #[automatically_derived] + impl #impl_gens ::arcana::es::event::Versioned for #ty#ty_gens + #where_clause + { + fn name() -> ::arcana::es::event::Name { + #event_name + } + + fn version() -> ::arcana::es::event::Version { + // SAFETY: Safe, as checked by proc macro in compile time. + unsafe { + ::arcana::es::event::Version::new_unchecked(#event_ver) + } + } + } + } + } + + /// Generates hidden machinery code used to check uniqueness of + /// [`Event::name`] and [`Event::version`]. + /// + /// [`Event::name`]: arcana_core::es::Event::name + /// [`Event::version`]: arcana_core::es::Event::version + pub fn gen_uniqueness_glue_code(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + + let (event_name, event_ver) = (&self.event_name, &self.event_version); + + quote! { + #[automatically_derived] + #[doc(hidden)] + impl #impl_gens ::arcana::codegen::UniqueEvents for #ty#ty_gens + #where_clause + { + #[doc(hidden)] + const COUNT: usize = 1; + } + + #[automatically_derived] + #[doc(hidden)] + impl #impl_gens #ty#ty_gens #where_clause { + #[doc(hidden)] + #[inline] + pub const fn __arcana_events() -> [(&'static str, u16); 1] { + [(#event_name, #event_ver)] + } + } + } + } +} + +#[cfg(test)] +mod spec { + use quote::quote; + use syn::parse_quote; + + #[test] + fn derives_struct_impl() { + let input = parse_quote! { + #[event(name = "event", version = 1)] + struct Event; + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::es::event::Versioned for Event { + fn name() -> ::arcana::es::event::Name { + "event" + } + + fn version() -> ::arcana::es::event::Version { + // SAFETY: Safe, as checked by proc macro in compile time. + unsafe { ::arcana::es::event::Version::new_unchecked(1) } + } + } + + #[automatically_derived] + #[doc(hidden)] + impl ::arcana::codegen::UniqueEvents for Event { + #[doc(hidden)] + const COUNT: usize = 1; + } + + #[automatically_derived] + #[doc(hidden)] + impl Event { + #[doc(hidden)] + pub const fn __arcana_events() -> [(&'static str, u16); 1] { + [("event", 1)] + } + } + }; + + assert_eq!( + super::derive(input).unwrap().to_string(), + output.to_string(), + ); + } + + #[test] + fn name_arg_is_required() { + let input = parse_quote! { + #[event(ver = 1)] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + format!("{}", err), + "`name` argument of `#[event]` attribute is expected to be \ + present, but is absent", + ); + } + + #[test] + fn version_arg_is_required() { + let input = parse_quote! { + #[event(name = "event")] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + format!("{}", err), + "either `version` or `ver` argument of `#[event]` attribute is \ + expected to be present, but is absent", + ); + } + + #[test] + fn errors_on_negative_version() { + let input = parse_quote! { + #[event(name = "event", ver = -1)] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!(format!("{}", err), "invalid digit found in string"); + } + + #[test] + fn errors_on_zero_version() { + let input = parse_quote! { + #[event(name = "event", version = 0)] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + format!("{}", err), + "number would be zero for non-zero type", + ); + } + + #[test] + fn errors_on_u16_overflowed_version() { + let input = parse_quote! { + #[event(name = "event", version = 4294967295)] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + format!("{}", err), + "number too large to fit in target type", + ); + } + + #[test] + fn errors_on_enum() { + let input = parse_quote! { + #[event(name = "event", version = 1)] + enum Event { + Event1(Event1), + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + format!("{}", err), + "expected struct only, \ + consider using `arcana::es::Event` for enums", + ); + } +} diff --git a/codegen/core/src/es/mod.rs b/codegen/core/src/es/mod.rs new file mode 100644 index 0000000..0234461 --- /dev/null +++ b/codegen/core/src/es/mod.rs @@ -0,0 +1,5 @@ +//! Code generation related to [Event Sourcing]. +//! +//! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html + +pub mod event; diff --git a/codegen/core/src/event/versioned.rs b/codegen/core/src/event/versioned.rs deleted file mode 100644 index 1100c41..0000000 --- a/codegen/core/src/event/versioned.rs +++ /dev/null @@ -1,304 +0,0 @@ -//! Definition of [`VersionedEvent`] derive macro for structs. -//! -//! [`VersionedEvent`]: arcana_core::VersionedEvent - -use std::{convert::TryFrom, num::NonZeroU16}; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::{spanned::Spanned as _, Result}; -use synthez::{ParseAttrs, ToTokens}; - -/// Derives [`VersionedEvent`] for struct. -/// -/// [`VersionedEvent`]: arcana_core::VersionedEvent -/// -/// # Errors -/// -/// - If `input` isn't a `struct`; -/// - If failed to parse [`Attrs`]. -pub fn derive(input: TokenStream) -> Result { - let input = syn::parse2::(input)?; - let definitions = Definitions::try_from(input)?; - - Ok(quote! { #definitions }) -} - -/// Attributes for [`VersionedEvent`] derive macro. -/// -/// [`VersionedEvent`]: arcana_core::VersionedEvent -#[derive(Debug, Default, ParseAttrs)] -pub struct Attrs { - /// Value for [`VersionedEvent::name()`] impl. - /// - /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() - #[parse(value)] - name: Option, - - /// Value for [`VersionedEvent::ver()`] impl. - /// - /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() - #[parse(value, validate = parses_to_non_zero_u16)] - version: Option, -} - -/// If `val` is [`Some`], checks if it can be parsed to [`NonZeroU16`]. -fn parses_to_non_zero_u16<'a>( - val: impl Into>, -) -> Result<()> { - val.into() - .map(syn::LitInt::base10_parse::) - .transpose() - .map(drop) -} - -/// Definition of [`VersionedEvent`] derive macro. -/// -/// [`VersionedEvent`]: arcana_core::Event -#[derive(ToTokens)] -#[to_tokens(append(impl_from, unique_event_name_and_ver))] -struct Definitions { - /// Struct's [`Ident`]. - /// - /// [`Ident`]: syn::Ident - ident: syn::Ident, - - /// Struct's [`Generics`]. - /// - /// [`Generics`]: syn::Generics - generics: syn::Generics, - - /// [`Attr::name`] from top-level struct attribute. - event_name: syn::LitStr, - - /// [`Attr::version`] from top-level struct attribute. - event_ver: syn::LitInt, -} - -impl Definitions { - /// Generates code to derive [`VersionedEvent`] by placing values from - /// [`Attrs`] inside [`VersionedEvent::name()`] and - /// [`VersionedEvent::ver()`] impls. - /// - /// [`VersionedEvent`]: arcana_core::VersionedEvent - /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() - /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() - fn impl_from(&self) -> TokenStream { - let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = - self.generics.split_for_impl(); - let (event_name, event_ver) = (&self.event_name, &self.event_ver); - - quote! { - #[automatically_derived] - impl #impl_generics ::arcana::VersionedEvent for - #name #ty_generics #where_clause - { - #[inline(always)] - fn name() -> ::arcana::EventName { - #event_name - } - - #[inline(always)] - fn ver() -> ::arcana::EventVersion { - // This is safe, because checked by proc-macro. - #[allow(unsafe_code)] - unsafe { ::arcana::EventVersion::new_unchecked(#event_ver) } - } - } - } - } - - /// Generates functions, that returns array of size 1 with - /// [`VersionedEvent::name()`] and [`VersionedEvent::ver()`]. Used for - /// uniqueness check. - /// - /// [`VersionedEvent::name()`]: arcana_core::VersionedEvent::name() - /// [`VersionedEvent::ver()`]: arcana_core::VersionedEvent::ver() - fn unique_event_name_and_ver(&self) -> TokenStream { - let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = - self.generics.split_for_impl(); - let (event_name, event_ver) = (&self.event_name, &self.event_ver); - - quote! { - #[automatically_derived] - impl #impl_generics ::arcana::codegen::UniqueEvents for - #name #ty_generics #where_clause - { - const COUNT: usize = 1; - } - - impl #impl_generics #name #ty_generics #where_clause { - #[automatically_derived] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [(#event_name, #event_ver)] - } - } - } - } -} - -impl TryFrom for Definitions { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> Result { - if !matches!(input.data, syn::Data::Struct(..)) { - return Err(syn::Error::new( - input.span(), - "Expected struct. Consider using arcana::Event for enums", - )); - } - - let attrs = Attrs::parse_attrs("event", &input)?; - let (event_name, event_ver) = match (attrs.name, attrs.version) { - (Some(event_name), Some(event_ver)) => (event_name, event_ver), - _ => { - return Err(syn::Error::new_spanned( - input, - "`name` and `version` arguments expected", - )) - } - }; - - Ok(Self { - ident: input.ident, - generics: input.generics, - event_name, - event_ver, - }) - } -} - -#[cfg(test)] -mod spec { - use super::{derive, quote}; - - #[test] - fn derives_struct_impl() { - let input = syn::parse_quote! { - #[event(name = "event", version = 1)] - struct Event; - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::VersionedEvent for Event { - #[inline(always)] - fn name() -> ::arcana::EventName { - "event" - } - - #[inline(always)] - fn ver() -> ::arcana::EventVersion { - // This is safe, because checked by proc-macro. - #[allow(unsafe_code)] - unsafe { ::arcana::EventVersion::new_unchecked(1) } - } - } - - #[automatically_derived] - impl ::arcana::codegen::UniqueEvents for Event { - const COUNT: usize = 1; - } - - impl Event { - #[automatically_derived] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [("event", 1)] - } - } - }; - - assert_eq!(derive(input).unwrap().to_string(), output.to_string()); - } - - #[test] - fn name_argument_is_expected() { - let input = syn::parse_quote! { - #[event(version = 1)] - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "`name` and `version` arguments expected", - ); - } - - #[test] - fn version_argument_is_expected() { - let input = syn::parse_quote! { - #[event(name = "event")] - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "`name` and `version` arguments expected", - ); - } - - #[test] - fn errors_on_negative_version() { - let input = syn::parse_quote! { - #[event(name = "event", version = -1)] - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!(format!("{}", error), "invalid digit found in string",); - } - - #[test] - fn errors_on_zero_version() { - let input = syn::parse_quote! { - #[event(name = "event", version = 0)] - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "number would be zero for non-zero type", - ); - } - - #[test] - fn errors_on_too_big_version() { - let input = syn::parse_quote! { - #[event(name = "event", version = 4294967295)] - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "number too large to fit in target type", - ); - } - - #[test] - fn errors_on_enum() { - let input = syn::parse_quote! { - #[event(name = "event", version = 1)] - enum Event { - Event1(Event1), - } - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "Expected struct. Consider using arcana::Event for enums", - ); - } -} diff --git a/codegen/core/src/lib.rs b/codegen/core/src/lib.rs index cee4505..b846905 100644 --- a/codegen/core/src/lib.rs +++ b/codegen/core/src/lib.rs @@ -20,4 +20,4 @@ unused_results )] -pub mod event; +pub mod es; diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 4279fea..41f4f80 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -2,13 +2,23 @@ name = "arcana-codegen-shim" version = "0.1.0-dev" edition = "2018" +resolver = "2" +description = "Internal proc macro shim of `arcana-codegen` crate." +authors = [ + "Ilya Solovyiov ", + "Kai Ren " +] +documentation = "https://docs.rs/arcana-codegen" +homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen" +repository = "https://github.com/arcana-rs/arcana" +readme = "README.md" [lib] proc-macro = true [dependencies] arcana-codegen-core = { version = "0.1.0-dev", path = "../core" } -arcana-core = { version = "0.1.0-dev", path = "../../core" } -[dependencies.syn] - version = "1.0" - default-features = false +syn = { version = "1.0.72", features = ["parsing", "proc-macro"], default-features = false } + +[dev-dependencies] +arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } diff --git a/codegen/shim/README.md b/codegen/shim/README.md index b4c8e23..0ce4e1e 100644 --- a/codegen/shim/README.md +++ b/codegen/shim/README.md @@ -1,2 +1,15 @@ arcana-codegen-shim =================== + +Internal [proc macro][1] shim of [`arcana-codegen`] crate. + +DO NOT use it directly, use [`arcana`] crate instead. + + + + + +[`arcana`]: https://docs.rs/arcana +[`arcana-codegen`]: https://docs.rs/arcana-codegen + +[1]: https://doc.rust-lang.org/reference/procedural-macros.html diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 4e675aa..a0a3e1b 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -20,24 +20,25 @@ unused_results )] +use arcana_codegen_core as codegen; use proc_macro::TokenStream; /// Macro for deriving [`Event`]. /// -/// [`Event`]: arcana_core::Event +/// [`Event`]: arcana_core::es::Event #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { - arcana_codegen_core::event::derive(input.into()) + codegen::event::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } -/// Macro for deriving [`VersionedEvent`]. +/// Macro for deriving [`event::Versioned`]. /// -/// [`VersionedEvent`]: arcana_core::VersionedEvent +/// [`event::Versioned`]: arcana_core::es::event::Versioned #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { - arcana_codegen_core::event::versioned::derive(input.into()) + codegen::event::versioned::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 261f015..6959b72 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -22,7 +22,7 @@ pub mod unique_events; -pub use static_assertions as sa; - #[doc(inline)] pub use arcana_codegen_shim::{Event, VersionedEvent}; +#[doc(hidden)] +pub use static_assertions as sa; diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index 988ca4f..30a62e7 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -1,35 +1,34 @@ -//! Utils for ensuring that every [`Event`] variant has a unique combination of -//! [`Event::name()`] and [`Event::ver()`]. +//! Utils for ensuring in compile time that every [`Event`] variant has a unique +//! combination of [`Event::name`] and [`Event::version`]. //! //! # Explanation //! -//! Main idea is that every [`Event`] or [`VersionedEvent`] deriver generates -//! `const fn __arcana_events() -> [(&'static str, u16); size]` method. This -//! array consists of [`EventName`]s and [`EventVersion`]s of all -//! [`VersionedEvent`]s. Uniqueness is checked with [`const_assert`] of -//! [`has_duplicates`]. -//! +//! Main idea is that every [`Event`] or [`event::Versioned`] deriving generates +//! a hidden `const fn __arcana_events() -> [(&'static str, u16); size]` method. +//! This array consists of [`event::Name`]s and [`event::Version`]s of all the +//! [`Event`] variants. Uniqueness is checked then with [`const_assert`]ing the +//! [`has_duplicates()`] function. //! //! [`const_assert`]: static_assertions::const_assert -//! [`Event`]: arcana_core::Event -//! [`Event::name()`]: arcana_core::Event::name() -//! [`Event::ver()`]: arcana_core::Event::ver() -//! [`EventName`]: arcana_core::EventName -//! [`EventVersion`]: arcana_core::EventVersion -//! [`VersionedEvent`]: arcana_core::VersionedEvent +//! [`Event`]: arcana_core::es::Event +//! [`Event::name`]: arcana_core::es::Event::name +//! [`Event::version`]: arcana_core::es::Event::version +//! [`event::Name`]: arcana_core::es::event::Name +//! [`event::Version`]: arcana_core::es::event::Version +//! [`event::Versioned`]: arcana_core::es::event::Versioned -/// Trait for keeping track of number of [`VersionedEvent`]s. +/// Tracking of number of [`VersionedEvent`]s. /// -/// [`VersionedEvent`]: arcana_core::VersionedEvent +/// [`VersionedEvent`]: arcana_core::es::VersionedEvent pub trait UniqueEvents { /// Number of [`VersionedEvent`]s in this [`Event`]. /// - /// [`Event`]: arcana_core::Event - /// [`VersionedEvent`]: arcana_core::VersionedEvent + /// [`Event`]: arcana_core::es::Event + /// [`VersionedEvent`]: arcana_core::es::VersionedEvent const COUNT: usize; } -/// Checks if array has duplicates. +/// Checks the given array of `events` combinations has duplicates. #[must_use] pub const fn has_duplicates(events: [(&str, u16); N]) -> bool { let mut outer = 0; @@ -49,12 +48,13 @@ pub const fn has_duplicates(events: [(&str, u16); N]) -> bool { false } -/// Compares strings constantly. +/// Compares strings in `const` context. /// /// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to /// write custom comparison function. /// /// [`Eq`]: std::cmp::Eq +// TODO: Remove once `Eq` trait is allowed in `const` context. #[must_use] const fn str_eq(l: &str, r: &str) -> bool { if l.len() != r.len() { diff --git a/core/Cargo.toml b/core/Cargo.toml index f57c1c7..8773411 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,11 +2,20 @@ name = "arcana-core" version = "0.1.0-dev" edition = "2018" +resolver = "2" +description = "Core abstractions and implementations of `arcana` crate." +authors = [ + "Ilya Solovyiov ", + "Kai Ren " +] +documentation = "https://docs.rs/arcana-core" +homepage = "https://github.com/arcana-rs/arcana/tree/master/core" +repository = "https://github.com/arcana-rs/arcana" +readme = "README.md" [features] -default = ["es"] es = [] [dependencies] -derive_more = "0.99" +derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false } ref-cast = "1.0" diff --git a/core/README.md b/core/README.md index d064704..45c6b61 100644 --- a/core/README.md +++ b/core/README.md @@ -1,2 +1,12 @@ arcana-core =========== + +Core abstractions and implementations of [`arcana`] crate. + +DO NOT use it directly, use [`arcana`] crate instead. + + + + + +[`arcana`]: https://docs.rs/arcana diff --git a/core/src/es/event.rs b/core/src/es/event.rs new file mode 100644 index 0000000..58e2cae --- /dev/null +++ b/core/src/es/event.rs @@ -0,0 +1,119 @@ +//! [`Event`] machinery. + +use std::{convert::TryFrom, num::NonZeroU16}; + +use derive_more::{Deref, DerefMut, Display, Into}; +use ref_cast::RefCast; + +/// Fully qualified name of an [`Event`]. +pub type Name = &'static str; + +/// Revision number of an [`Event`]. +#[derive( + Clone, Copy, Debug, Display, Eq, Hash, Into, Ord, PartialEq, PartialOrd, +)] +// TODO: Should it be bigger? Allow to abstract over it? +pub struct Version(NonZeroU16); + +impl Version { + /// Creates a new [`Version`] out of the given `value`. + /// + /// The given `value` should not be `0` (zero) and fit into [`u16`] size. + #[must_use] + pub fn try_new(value: N) -> Option + where + u16: TryFrom, + { + Some(Self(NonZeroU16::new(u16::try_from(value).ok()?)?)) + } + + /// Creates a new [`Version`] out of the given `value` without checking its + /// invariants. + /// + /// # Safety + /// + /// The given `value` must not be `0` (zero). + #[inline] + #[must_use] + pub const unsafe fn new_unchecked(value: u16) -> Self { + Self(NonZeroU16::new_unchecked(value)) + } +} + +/// [`Event`] of a concrete [`Version`]. +pub trait Versioned { + /// Returns [`Name`] of this [`Event`]. + /// + /// _Note:_ This should effectively be a constant value, and should never + /// change. + #[must_use] + fn name() -> Name; + + /// Returns [`Version`] of this [`Event`]. + /// + /// _Note:_ This should effectively be a constant value, and should never + /// change. + #[must_use] + fn version() -> Version; +} + +/// [Event Sourcing] event describing something that has occurred (happened +/// fact). +/// +/// [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html +pub trait Event { + /// Returns [`Name`] of this [`Event`]. + /// + /// _Note:_ This should effectively be a constant value, and should never + /// change. + #[must_use] + fn name(&self) -> Name; + + /// Returns [`Version`] of this [`Event`]. + #[must_use] + fn version(&self) -> Version; +} + +impl Event for Ev { + fn name(&self) -> Name { + ::name() + } + + fn version(&self) -> Version { + ::version() + } +} + +/// State that can be calculated by applying the specified [`Event`]. +pub trait Sourced { + /// Applies the specified [`Event`] to the current state. + fn apply(&mut self, event: &Ev); +} + +impl> Sourced for Option { + fn apply(&mut self, event: &Ev) { + if let Some(state) = self { + state.apply(event); + } + } +} + +/// Before a state can be [`Sourced`] it needs to be [`Initialized`]. +pub trait Initialized { + /// Creates an initial state from the given [`Event`]. + fn init(event: &Ev) -> Self; +} + +/// Wrapper type to mark an [`Event`] that makes some [`Sourced`] state being +/// [`Initialized`]. +#[derive(Clone, Copy, Debug, Deref, DerefMut, Display, RefCast)] +#[repr(transparent)] +pub struct Initial(pub Ev); + +impl> Sourced> + for Option +{ + fn apply(&mut self, event: &Initial) { + *self = Some(S::init(&event.0)); + } +} diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 5931441..16464f5 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -1,124 +1,12 @@ -//! [Event Sourcing] related definitions. +//! Abstractions and tools for [Event Sourcing]. //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html -use std::{convert::TryFrom, num::NonZeroU16}; +pub mod event; -use derive_more::{Display, Into}; -use ref_cast::RefCast; - -/// [Event Sourcing] event that describes something that has occurred (happened -/// fact). -/// -/// [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html -pub trait Event { - /// Returns [`Name`] of this [`Event`]. - /// - /// _Note:_ This should effectively be a constant value, and should never - /// change. - #[must_use] - fn name(&self) -> Name; - - /// Returns [`Version`] of this [`Event`]. - #[must_use] - fn ver(&self) -> Version; -} - -/// Versioned [`Event`]. -/// -/// The single type of [`Event`] may have different versions, which allows -/// evolving [`Event`] in the type. To overcome the necessity of dealing with -/// multiple types of the same [`Event`], it's recommended for the last actual -/// version of [`Event`] to implement trait [`From`] its previous versions, so -/// they can be automatically transformed into the latest actual version of -pub trait Versioned { - /// Returns [`Name`] of this [`Event`]. - /// - /// _Note:_ This should effectively be a constant value, and should never - /// change. - #[must_use] - fn name() -> Name; - - /// Returns [`Version`] of this [`Event`]. - #[must_use] - fn ver() -> Version; -} - -/// Fully qualified name of an [`Event`]. -pub type Name = &'static str; - -/// Revision number of an [`Event`]. -#[derive( - Clone, Copy, Debug, Display, Eq, Hash, Into, Ord, PartialEq, PartialOrd, -)] -pub struct Version(NonZeroU16); - -impl Version { - /// Creates a new [`Version`] out of the given `val`ue. - /// - /// The given value should not be `0` (zero) and fit into [`u16`] size. - #[inline] - #[must_use] - pub fn try_new(val: N) -> Option - where - u16: TryFrom, - { - Some(Self(NonZeroU16::new(u16::try_from(val).ok()?)?)) - } - - /// Creates a new [`Version`] out of the given `val`ue without checking its - /// invariants. - /// - /// # Safety - /// - /// The given `val`ue must not be `0` (zero). - #[allow(unsafe_code)] - #[inline] - #[must_use] - pub const unsafe fn new_unchecked(val: u16) -> Self { - Self(NonZeroU16::new_unchecked(val)) - } -} - -impl Event for Ev { - fn name(&self) -> Name { - ::name() - } - - fn ver(&self) -> Version { - ::ver() - } -} - -/// State that can be calculated by applying specified [`Event`]. -pub trait Sourced { - /// Applies given [`Event`] to the current state. - fn apply(&mut self, event: &Ev); -} - -impl> Sourced for Option { - fn apply(&mut self, event: &Ev) { - if let Some(state) = self { - state.apply(event); - } - } -} - -/// Before items can be [`Sourced`], they need to be [`Initialized`]. -pub trait Initialized { - /// Creates initial state from given [`Event`]. - fn init(event: &Ev) -> Self; -} - -/// Wrapper-type intended for [`Event`]s that can initialize [`Sourced`] items. -#[derive(Clone, Copy, Debug, Display, RefCast)] -#[repr(transparent)] -pub struct Initial(pub Ev); - -impl> Sourced> - for Option -{ - fn apply(&mut self, event: &Initial) { - *self = Some(S::init(&event.0)); - } -} +#[doc(inline)] +pub use self::event::{ + Event, Initial as InitialEvent, Initialized as EventInitialized, + Name as EventName, Sourced as EventSourced, Version as EventVersion, + Versioned as VersionedEvent, +}; diff --git a/core/src/lib.rs b/core/src/lib.rs index 6d1e692..14848ce 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,8 +5,7 @@ rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links, trivial_casts, - trivial_numeric_casts, - unsafe_code + trivial_numeric_casts )] #![forbid(non_ascii_idents)] #![warn( @@ -22,12 +21,4 @@ )] #[cfg(feature = "es")] -mod es; - -#[doc(inline)] -#[cfg(feature = "es")] -pub use es::{ - Event, Initial as InitialEvent, Initialized as EventInitialized, - Name as EventName, Sourced as EventSourced, Version as EventVersion, - Versioned as VersionedEvent, -}; +pub mod es; diff --git a/src/lib.rs b/src/lib.rs index 5d532bf..09244b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,10 +26,7 @@ pub mod codegen; #[doc(inline)] #[cfg(feature = "es")] -pub use arcana_core::{ - Event, EventInitialized, EventName, EventSourced, EventVersion, - InitialEvent, VersionedEvent, -}; +pub use arcana_core::es; /// Macro for deriving [`Event`](trait@Event) on enums. For structs consider /// [`VersionedEvent`](macro@VersionedEvent). From 088db1ae554e4691384ce6c4abe0d34852403844 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 25 Aug 2021 09:08:11 +0300 Subject: [PATCH 031/104] Corrections --- Cargo.toml | 4 + codegen/Cargo.toml | 4 +- codegen/core/Cargo.toml | 5 +- codegen/core/src/es/event/mod.rs | 347 ++++++++++--------------- codegen/core/src/es/event/versioned.rs | 7 +- codegen/shim/Cargo.toml | 4 +- codegen/shim/src/lib.rs | 4 +- codegen/src/es/event.rs | 4 + codegen/src/es/mod.rs | 5 + codegen/src/lib.rs | 3 +- examples/event.rs | 6 +- src/es/event.rs | 109 ++++++++ src/es/mod.rs | 12 + src/lib.rs | 102 +------- 14 files changed, 285 insertions(+), 331 deletions(-) create mode 100644 codegen/src/es/event.rs create mode 100644 codegen/src/es/mod.rs create mode 100644 src/es/event.rs create mode 100644 src/es/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 0961edb..d0480ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,7 @@ arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } [workspace] members = ["codegen", "codegen/core", "codegen/shim", "core"] + +[[example]] +name = "event" +required-features = ["derive", "es"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index a00ce95..3fefdd1 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -15,7 +15,5 @@ readme = "README.md" [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim"} -static_assertions = { version = "1.1", default-features = false } - -[dev-dependencies] arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"] } +static_assertions = { version = "1.1", default-features = false } diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml index 57d2122..0cf2e2a 100644 --- a/codegen/core/Cargo.toml +++ b/codegen/core/Cargo.toml @@ -14,11 +14,8 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [dependencies] +arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } proc-macro2 = { version = "1.0", default-features = false } quote = { version = "1.0", default-features = false } -strum = { version = "0.21", features = ["derive"] } syn = { version = "1.0", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } synthez = { version = "0.1", default-features = false } - -[dev-dependencies] -arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } diff --git a/codegen/core/src/es/event/mod.rs b/codegen/core/src/es/event/mod.rs index aa83a8e..280fb93 100644 --- a/codegen/core/src/es/event/mod.rs +++ b/codegen/core/src/es/event/mod.rs @@ -2,26 +2,20 @@ pub mod versioned; -use std::{convert::TryFrom, str::FromStr as _}; +use std::convert::TryFrom; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use strum::{EnumString, EnumVariantNames, VariantNames as _}; -use syn::{ - parse::{Parse, ParseStream}, - spanned::Spanned, -}; +use syn::spanned::Spanned; use synthez::{ParseAttrs, ToTokens}; -/// Derives [`Event`] for enum. -/// -/// [`Event`]: arcana_core::Event +/// Expands `#[derive(Event)]` macro. /// /// # Errors /// /// - If `input` isn't an `enum`; /// - If `enum` variant consist not from single event; -/// - If failed to parse [`Attrs`]. +/// - If failed to parse [`VariantAttrs`]. pub fn derive(input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; let definitions = Definition::try_from(input)?; @@ -29,103 +23,43 @@ pub fn derive(input: TokenStream) -> syn::Result { Ok(quote! { #definitions }) } -/// Attributes for [`Event`] derive macro. +/// Attributes for enum variant deriving [`Event`]. /// -/// [`Event`]: arcana_core::Event +/// [`Event`]: arcana_core::es::Event #[derive(Debug, Default, ParseAttrs)] -pub struct Attrs { - /// `#[event(skip(...))` attribute. - #[parse(value)] - skip: Option>, -} - -impl Attrs { - /// Checks whether variant or whole container shouldn't be checked for - /// [`Event::name()`] and [`Event::ver()`] uniqueness. +pub struct VariantAttrs { + /// If present, [`Event`] impl and uniqueness check will be skipped for + /// particular enum variant. /// - /// [`Event::name()`]: arcana_core::Event::name() - /// [`Event::ver()`]: arcana_core::Event::ver() - #[must_use] - pub fn skip_check_unique_name_and_ver(&self) -> bool { - matches!( - self.skip.as_ref().map(|sp| sp.item), - Some(SkipAttr::CheckUniqueNameAndVer), - ) - } -} - -/// Wrapper for storing [`Span`]. -/// -/// We don't use one from [`synthez`], as we can't derive [`Parse`] with our `T` -/// inside. -#[derive(Clone, Debug)] -struct Spanning { - item: T, - span: Span, -} - -impl Spanned for Spanning { - fn span(&self) -> Span { - self.span - } -} - -/// Inner value for `#[event(skip(...))]` attribute. -#[derive(Clone, Copy, Debug, EnumString, EnumVariantNames)] -#[strum(serialize_all = "snake_case")] -pub enum SkipAttr { - /// Variant for skipping uniqueness check of [`Event::name()`] and - /// [`Event::ver()`]. - /// - /// [`Event::name()`]: arcana_core::Event::name() - /// [`Event::ver()`]: arcana_core::Event::ver() - CheckUniqueNameAndVer, -} - -impl Parse for Spanning { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = syn::Ident::parse(input)?; - Ok(Spanning { - item: SkipAttr::from_str(&ident.to_string()).map_err(|_| { - syn::Error::new( - ident.span(), - &format!( - "Unknown value. Allowed values: {}", - SkipAttr::VARIANTS.join(", "), - ), - ) - })?, - span: ident.span(), - }) - } + /// [`Event`]: arcana_core::es::Event + #[parse(ident, alias = ignore)] + pub skip: Option, } /// Definition of [`Event`] derive macro. /// -/// [`Event`]: arcana_core::Event -#[derive(ToTokens)] +/// [`Event`]: arcana_core::es::Event +#[derive(Debug, ToTokens)] #[to_tokens(append(impl_from, unique_event_name_and_ver))] -struct Definition { +pub struct Definition { /// Enum's [`Ident`]. /// - /// [`Ident`]: syn::Ident - ident: syn::Ident, + /// [`Ident`]: struct@syn::Ident + pub ident: syn::Ident, /// Enum's [`Generics`]. /// /// [`Generics`]: syn::Generics - generics: syn::Generics, + pub generics: syn::Generics, - /// Enum's [`Variant`]s alongside with parsed [`Attrs`]. + /// Enum's [`Variant`]s alongside with parsed [`VariantAttrs`]. /// - /// Every [`Variant`] has exactly 1 [`Field`]. + /// Every [`Variant`] should have exactly 1 [`Field`] in case they are not + /// marked with `#[event(skip)]` attribute. /// /// [`Field`]: syn::Field /// [`Variant`]: syn::Variant - variants: Vec<(syn::Variant, Attrs)>, - - /// Enum's top-level [`Attrs`]. - attrs: Attrs, + pub variants: Vec<(syn::Variant, VariantAttrs)>, } impl TryFrom for Definition { @@ -137,26 +71,25 @@ impl TryFrom for Definition { } else { return Err(syn::Error::new( input.span(), - "Expected enum. \ - Consider using arcana::VersionedEvent for structs", + "expected enum only, \ + consider using `arcana::es::event::Versioned` for structs", )); }; - for variant in &data.variants { - if variant.fields.len() != 1 { - return Err(syn::Error::new( - variant.span(), - "Enum variants must have exactly 1 field", - )); - } - } - - let attrs = Attrs::parse_attrs("event", &input)?; let variants = data .variants .iter() .map(|variant| { - Ok((variant.clone(), Attrs::parse_attrs("event", variant)?)) + let attrs = VariantAttrs::parse_attrs("event", variant)?; + + if variant.fields.len() != 1 && attrs.skip.is_none() { + return Err(syn::Error::new( + variant.span(), + "enum variants must have exactly 1 field", + )); + } + + Ok((variant.clone(), attrs)) }) .collect::>()?; @@ -164,7 +97,6 @@ impl TryFrom for Definition { ident: input.ident, generics: input.generics, variants, - attrs, }) } } @@ -173,62 +105,78 @@ impl Definition { /// Generates code to derive [`Event`] by simply matching over every enum /// variant, which is expected to be itself [`Event`] deriver. /// - /// [`Event`]: arcana_core::Event - fn impl_from(&self) -> TokenStream { + /// # Panics + /// + /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked + /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. + /// + /// [`Event`]: arcana_core::es::event::Event + /// [`Field`]: syn::Field + /// [`Variant`]: syn::Variant + #[must_use] + pub fn impl_from(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let (event_names, event_versions): (TokenStream, TokenStream) = self .variants .iter() - .map(|(variant, _)| { + .filter_map(|(variant, attrs)| { + if attrs.skip.is_some() { + return None; + } + let name = &variant.ident; let generate_variant = |func: TokenStream| match &variant.fields { syn::Fields::Named(named) => { - // Unwrapping is safe here as we checked for - // `.len() == 1` in TryFrom impl. let field = &named.named.iter().next().unwrap().ident; quote! { Self::#name { #field } => { - ::arcana::Event::#func(#field) + ::arcana::es::Event::#func(#field) } } } syn::Fields::Unnamed(_) => { quote! { Self::#name(inner) => { - ::arcana::Event::#func(inner) + ::arcana::es::Event::#func(inner) } } } syn::Fields::Unit => unreachable!(), }; - ( + Some(( generate_variant(quote! { name }), - generate_variant(quote! { ver }), - ) + generate_variant(quote! { version }), + )) }) .unzip(); + let unreachable_for_skip = self + .variants + .iter() + .any(|(_, attr)| attr.skip.is_some()) + .then(|| quote! { _ => unreachable!()}); + quote! { #[automatically_derived] - impl #impl_generics ::arcana::Event for + impl #impl_generics ::arcana::es::Event for #name #ty_generics #where_clause { - #[inline(always)] - fn name(&self) -> ::arcana::EventName { + fn name(&self) -> ::arcana::es::event::Name { match self { #event_names + #unreachable_for_skip } } - #[inline(always)] - fn ver(&self) -> ::arcana::EventVersion { + fn version(&self) -> ::arcana::es::event::Version { match self { #event_versions + #unreachable_for_skip } } } @@ -236,17 +184,21 @@ impl Definition { } /// Generates functions, that returns array composed from arrays of all enum - /// variants. Resulting array size is count of all [`VersionedEvent`]s. + /// variants. /// - /// Checks uniqueness of all [`Event::name`]s and [`Event::ver`]s. + /// Checks uniqueness of all [`Event::name`][0]s and [`Event::version`][1]s. /// - /// [`Event::name`]: arcana_core::Event::name() - /// [`Event::ver`]: arcana_core::Event::ver() - fn unique_event_name_and_ver(&self) -> TokenStream { - if self.attrs.skip_check_unique_name_and_ver() { - return TokenStream::new(); - } - + /// # Panics + /// + /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked + /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. + /// + /// [0]: arcana_core::es::event::Event::name() + /// [1]: arcana_core::es::event::Event::version() + /// [`Field`]: syn::Field + /// [`Variant`]: syn::Variant + #[must_use] + pub fn unique_event_name_and_ver(&self) -> TokenStream { let name = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); @@ -257,9 +209,7 @@ impl Definition { .variants .iter() .filter_map(|(variant, attr)| { - (!attr.skip_check_unique_name_and_ver()).then(|| { - // Unwrapping is safe here as we checked for `.len() == 1` - // in TryFrom impl. + attr.skip.is_none().then(|| { let ty = &variant.fields.iter().next().unwrap().ty; ( quote! { @@ -296,6 +246,7 @@ impl Definition { impl #impl_generics #name #ty_generics #where_clause { #[automatically_derived] + #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); ::COUNT @@ -330,7 +281,7 @@ mod spec { fn derives_enum_impl() { let input = syn::parse_quote! { enum Event { - Event1(EventUnnamend), + Event1(EventUnnamed), Event2 { event: EventNamed, } @@ -339,27 +290,25 @@ mod spec { let output = quote! { #[automatically_derived] - impl ::arcana::Event for Event { - #[inline(always)] - fn name(&self) -> ::arcana::EventName { + impl ::arcana::es::Event for Event { + fn name(&self) -> ::arcana::es::event::Name { match self { Self::Event1(inner) => { - ::arcana::Event::name(inner) + ::arcana::es::Event::name(inner) } Self::Event2 { event } => { - ::arcana::Event::name(event) + ::arcana::es::Event::name(event) } } } - #[inline(always)] - fn ver(&self) -> ::arcana::EventVersion { + fn version(&self) -> ::arcana::es::event::Version { match self { Self::Event1(inner) => { - ::arcana::Event::ver(inner) + ::arcana::es::Event::version(inner) } Self::Event2 { event } => { - ::arcana::Event::ver(event) + ::arcana::es::Event::version(event) } } } @@ -368,12 +317,13 @@ mod spec { #[automatically_derived] impl ::arcana::codegen::UniqueEvents for Event { const COUNT: usize = - ::COUNT + + ::COUNT + ::COUNT; } impl Event { #[automatically_derived] + #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); ::COUNT @@ -386,7 +336,7 @@ mod spec { let mut global = 0; { - let ev = EventUnnamend::__arcana_events(); + let ev = EventUnnamed::__arcana_events(); let mut local = 0; while local < ev.len() { res[global] = ev[local]; @@ -420,85 +370,55 @@ mod spec { } #[test] - fn skip_unique_check_on_container() { - let input = syn::parse_quote! { - #[event(skip(check_unique_name_and_ver))] + fn skip_unique_check_on_variant() { + let input_skip = syn::parse_quote! { enum Event { - Event1(EventUnnamend), + Event1(EventUnnamed), Event2 { event: EventNamed, - } - } - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::Event for Event { - #[inline(always)] - fn name(&self) -> ::arcana::EventName { - match self { - Self::Event1(inner) => { - ::arcana::Event::name(inner) - } - Self::Event2 { event } => { - ::arcana::Event::name(event) - } - } - } - - #[inline(always)] - fn ver(&self) -> ::arcana::EventVersion { - match self { - Self::Event1(inner) => { - ::arcana::Event::ver(inner) - } - Self::Event2 { event } => { - ::arcana::Event::ver(event) - } - } - } + }, + #[event(skip)] + #[doc(hidden)] + _NonExhaustive } }; - assert_eq!(derive(input).unwrap().to_string(), output.to_string()); - } - - #[test] - fn skip_unique_check_on_variant() { - let input = syn::parse_quote! { + let input_ignore = syn::parse_quote! { enum Event { - #[event(skip(check_unique_name_and_ver))] - Event1(EventUnnamend), + Event1(EventUnnamed), Event2 { event: EventNamed, - } + }, + #[event(ignore)] + #[doc(hidden)] + _NonExhaustive } }; let output = quote! { #[automatically_derived] - impl ::arcana::Event for Event { - #[inline(always)] - fn name(&self) -> ::arcana::EventName { + impl ::arcana::es::Event for Event { + fn name(&self) -> ::arcana::es::event::Name { match self { Self::Event1(inner) => { - ::arcana::Event::name(inner) + ::arcana::es::Event::name(inner) } Self::Event2 { event } => { - ::arcana::Event::name(event) + ::arcana::es::Event::name(event) } + _ => unreachable!() } } - #[inline(always)] - fn ver(&self) -> ::arcana::EventVersion { + fn version(&self) -> ::arcana::es::event::Version { match self { Self::Event1(inner) => { - ::arcana::Event::ver(inner) + ::arcana::es::Event::version(inner) } Self::Event2 { event } => { - ::arcana::Event::ver(event) + ::arcana::es::Event::version(event) } + _ => unreachable!() } } } @@ -506,11 +426,13 @@ mod spec { #[automatically_derived] impl ::arcana::codegen::UniqueEvents for Event { const COUNT: usize = + ::COUNT + ::COUNT; } impl Event { #[automatically_derived] + #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); ::COUNT @@ -522,6 +444,16 @@ mod spec { let mut global = 0; + { + let ev = EventUnnamed::__arcana_events(); + let mut local = 0; + while local < ev.len() { + res[global] = ev[local]; + local += 1; + global += 1; + } + } + { let ev = EventNamed::__arcana_events(); let mut local = 0; @@ -543,7 +475,10 @@ mod spec { ); }; - assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + let input_skip = derive(input_skip).unwrap().to_string(); + let input_ignore = derive(input_ignore).unwrap().to_string(); + assert_eq!(input_skip, input_ignore); + assert_eq!(input_skip, output.to_string()); } #[test] @@ -562,24 +497,7 @@ mod spec { assert_eq!( format!("{}", error), - "Enum variants must have exactly 1 field", - ); - } - - #[test] - fn errors_on_unknown_attribute_value() { - let input = syn::parse_quote! { - enum Event { - #[event(skip(unknown))] - Event1(Event1), - } - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "Unknown value. Allowed values: check_unique_name_and_ver", + "enum variants must have exactly 1 field", ); } @@ -593,7 +511,8 @@ mod spec { assert_eq!( format!("{}", error), - "Expected enum. Consider using arcana::VersionedEvent for structs", + "expected enum only, \ + consider using `arcana::es::event::Versioned` for structs", ); } } diff --git a/codegen/core/src/es/event/versioned.rs b/codegen/core/src/es/event/versioned.rs index cc4e3b8..39dfba5 100644 --- a/codegen/core/src/es/event/versioned.rs +++ b/codegen/core/src/es/event/versioned.rs @@ -48,7 +48,9 @@ fn parses_as_non_zero_u16(val: &Required) -> syn::Result<()> { #[derive(Debug, ToTokens)] #[to_tokens(append(impl_event_versioned, gen_uniqueness_glue_code))] pub struct Definition { - /// [`syn::Ident`] of this structure's type. + /// [`Ident`] of this structure's type. + /// + /// [`Ident`]: struct@syn::Ident pub ident: syn::Ident, /// [`syn::Generics`] of this structure's type. @@ -95,6 +97,7 @@ impl Definition { /// Generates code to derive [`event::Versioned`][0] trait. /// /// [0]: arcana_core::es::event::Versioned + #[must_use] pub fn impl_event_versioned(&self) -> TokenStream { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); @@ -125,6 +128,7 @@ impl Definition { /// /// [`Event::name`]: arcana_core::es::Event::name /// [`Event::version`]: arcana_core::es::Event::version + #[must_use] pub fn gen_uniqueness_glue_code(&self) -> TokenStream { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); @@ -190,6 +194,7 @@ mod spec { #[doc(hidden)] impl Event { #[doc(hidden)] + #[inline] pub const fn __arcana_events() -> [(&'static str, u16); 1] { [("event", 1)] } diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 41f4f80..a02a015 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -18,7 +18,5 @@ proc-macro = true [dependencies] arcana-codegen-core = { version = "0.1.0-dev", path = "../core" } -syn = { version = "1.0.72", features = ["parsing", "proc-macro"], default-features = false } - -[dev-dependencies] arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } +syn = { version = "1.0.72", features = ["parsing", "proc-macro"], default-features = false } diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index a0a3e1b..b8f7b00 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -28,7 +28,7 @@ use proc_macro::TokenStream; /// [`Event`]: arcana_core::es::Event #[proc_macro_derive(Event, attributes(event))] pub fn derive_event(input: TokenStream) -> TokenStream { - codegen::event::derive(input.into()) + codegen::es::event::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -38,7 +38,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream { /// [`event::Versioned`]: arcana_core::es::event::Versioned #[proc_macro_derive(VersionedEvent, attributes(event))] pub fn derive_versioned_event(input: TokenStream) -> TokenStream { - codegen::event::versioned::derive(input.into()) + codegen::es::event::versioned::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/es/event.rs b/codegen/src/es/event.rs new file mode 100644 index 0000000..c092e50 --- /dev/null +++ b/codegen/src/es/event.rs @@ -0,0 +1,4 @@ +//! [`Event`] machinery. + +#[doc(inline)] +pub use arcana_codegen_shim::{Event, VersionedEvent as Versioned}; diff --git a/codegen/src/es/mod.rs b/codegen/src/es/mod.rs new file mode 100644 index 0000000..a806fb9 --- /dev/null +++ b/codegen/src/es/mod.rs @@ -0,0 +1,5 @@ +//! Abstractions and tools for [Event Sourcing]. +//! +//! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html + +pub mod event; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 6959b72..6706a99 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -20,9 +20,8 @@ unused_results )] +pub mod es; pub mod unique_events; -#[doc(inline)] -pub use arcana_codegen_shim::{Event, VersionedEvent}; #[doc(hidden)] pub use static_assertions as sa; diff --git a/examples/event.rs b/examples/event.rs index 14d189b..7550336 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -1,10 +1,10 @@ -use arcana::{Event, VersionedEvent}; +use arcana::es::{event, Event}; -#[derive(VersionedEvent)] +#[derive(event::Versioned)] #[event(name = "chat", version = 1)] struct ChatEvent; -#[derive(VersionedEvent)] +#[derive(event::Versioned)] #[event(name = "file", version = 1)] struct FileEvent; diff --git a/src/es/event.rs b/src/es/event.rs new file mode 100644 index 0000000..a6d3cbd --- /dev/null +++ b/src/es/event.rs @@ -0,0 +1,109 @@ +//! [`Event`] machinery. + +#[doc(inline)] +pub use arcana_core::es::event::{ + Event, Initial, Initialized, Name, Sourced, Version, Versioned, +}; + +/// Macro for deriving [`Event`] on enums. For structs consider +/// [`Versioned`](macro@Versioned). +/// +/// This macro ensures that every combination of [`Event::name()`][0] and +/// [`Event::version()`][1] are unique. The only limitation is that every +/// underlying [`Event`] or [`Versioned`](trait@Versioned) impls should be +/// generated with proc macros. +/// +/// # Attribute arguments +/// +/// - `#[event(skip)]` — optional +/// +/// Use this value on particular enum variant to skip [`Event`] impl for it +/// and check for unique combination of [`Event::name()`][0] and +/// [`Event::version()`][1]. +/// __Note__: calling [`Event::name()`][0] or [`Event::version()`][1] on those +/// variants will result in [`unreachable!()`] panic. +/// +/// # Examples +/// +/// ```compile_fail,E0080 +/// # use arcana::es::{Event, event}; +/// # +/// #[derive(event::Versioned)] +/// #[event(name = "chat", version = 1)] +/// struct ChatEvent; +/// +/// #[derive(event::Versioned)] +/// #[event(name = "file", version = 1)] +/// struct FileEvent; +/// +/// #[derive(Event)] +/// enum AnyEvent { +/// Chat(ChatEvent), +/// File { event: FileEvent }, +/// } +/// +/// #[derive(Event)] +/// enum DuplicatedEvent { +/// Any(AnyEvent), +/// File(FileEvent), +/// } +/// ``` +/// +/// ``` +/// # use arcana::es::{Event, event}; +/// # +/// # #[derive(event::Versioned)] +/// # #[event(name = "chat", version = 1)] +/// # struct ChatEvent; +/// # +/// # #[derive(event::Versioned)] +/// # #[event(name = "file", version = 1)] +/// # struct FileEvent; +/// # +/// # #[derive(Event)] +/// # enum AnyEvent { +/// # Chat(ChatEvent), +/// # File { event: FileEvent }, +/// # } +/// # +/// #[derive(Event)] +/// enum DuplicatedEvent { +/// Any(AnyEvent), +/// #[event(skip)] +/// File(FileEvent), +/// } +/// ``` +/// +/// [0]: trait@Event::name() +/// [1]: trait@Event::version() +/// [`Event`]: trait@Event +#[cfg(feature = "derive")] +pub use arcana_codegen::es::event::Event; + +/// Macro for deriving [`Versioned`](trait@Versioned) on structs. For +/// enums, consisting of different events consider [`Event`](macro@Event). +/// +/// # Attribute arguments +/// +/// - `#[event(name = "...")]` — required +/// +/// Value used in [`Versioned::name()`][0] impl. +/// +/// - `#[event(ver = NonZeroU16)]` — required +/// +/// Value used in [`Versioned::version()`][1] impl. +/// +/// # Examples +/// +/// ``` +/// # use arcana::es::event; +/// # +/// #[derive(event::Versioned)] +/// #[event(name = "event", version = 1)] +/// struct Event; +/// ``` +/// +/// [0]: trait@Versioned::name() +/// [1]: trait@Versioned::version() +#[cfg(feature = "derive")] +pub use arcana_codegen::es::event::Versioned; diff --git a/src/es/mod.rs b/src/es/mod.rs new file mode 100644 index 0000000..16464f5 --- /dev/null +++ b/src/es/mod.rs @@ -0,0 +1,12 @@ +//! Abstractions and tools for [Event Sourcing]. +//! +//! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html + +pub mod event; + +#[doc(inline)] +pub use self::event::{ + Event, Initial as InitialEvent, Initialized as EventInitialized, + Name as EventName, Sourced as EventSourced, Version as EventVersion, + Versioned as VersionedEvent, +}; diff --git a/src/lib.rs b/src/lib.rs index 09244b2..f537241 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,105 +20,9 @@ unused_results )] -#[doc(hidden)] -#[cfg(all(feature = "derive", feature = "es"))] -pub mod codegen; - -#[doc(inline)] #[cfg(feature = "es")] -pub use arcana_core::es; +pub mod es; -/// Macro for deriving [`Event`](trait@Event) on enums. For structs consider -/// [`VersionedEvent`](macro@VersionedEvent). -/// -/// This macro ensures that every combination of [`Event::name()`](trait@Event) -/// and [`Event::ver()`](trait@Event) are unique. The only limitation is that -/// every underlying [`Event`](trait@Event) or -/// [`VersionedEvent`](trait@VersionedEvent) impls should be generated with proc -/// macros. -/// -/// # Attribute arguments -/// -/// - `#[event(skip(check_unique_name_and_ver))]` — optional -/// -/// Use this value on whole container or particular enum variant to skip check -/// for unique combination of [`Event::name()`](trait@Event) and -/// [`Event::ver()`](trait@Event). -/// -/// # Examples -/// -/// ```compile_fail,E0080 -/// # use arcana::{Event, VersionedEvent}; -/// # -/// #[derive(VersionedEvent)] -/// #[event(name = "chat", version = 1)] -/// struct ChatEvent; -/// -/// #[derive(VersionedEvent)] -/// #[event(name = "file", version = 1)] -/// struct FileEvent; -/// -/// #[derive(Event)] -/// enum AnyEvent { -/// Chat(ChatEvent), -/// File { event: FileEvent }, -/// } -/// -/// #[derive(Event)] -/// enum DuplicatedEvent { -/// Any(AnyEvent), -/// File(FileEvent), -/// } -/// ``` -/// -/// ``` -/// # use arcana::{Event, VersionedEvent}; -/// # -/// # #[derive(VersionedEvent)] -/// # #[event(name = "chat", version = 1)] -/// # struct ChatEvent; -/// # -/// # #[derive(VersionedEvent)] -/// # #[event(name = "file", version = 1)] -/// # struct FileEvent; -/// # -/// # #[derive(Event)] -/// # enum AnyEvent { -/// # Chat(ChatEvent), -/// # File { event: FileEvent }, -/// # } -/// # -/// #[derive(Event)] -/// enum DuplicatedEvent { -/// Any(AnyEvent), -/// #[event(skip(check_unique_name_and_ver))] -/// File(FileEvent), -/// } -/// ``` -#[cfg(all(feature = "derive", feature = "es"))] -pub use arcana_codegen::Event; - -/// Macro for deriving [`VersionedEvent`](trait@VersionedEvent) on structs. For -/// enums, consisting of different events consider [`Event`](macro@Event). -/// -/// # Attribute arguments -/// -/// - `#[event(name = "...")]` — required -/// -/// Value used in [`VersionedEvent::name()`](trait@VersionedEvent) impl. -/// -/// - `#[event(ver = NonZeroU16)]` — required -/// -/// Value used in [`VersionedEvent::ver()`](trait@VersionedEvent) impl. -/// -/// # Examples -/// -/// ``` -/// # use arcana::VersionedEvent; -/// # -/// #[derive(VersionedEvent)] -/// #[event(name = "event", version = 1)] -/// struct Event; -/// ``` +#[doc(hidden)] #[cfg(all(feature = "derive", feature = "es"))] -pub use arcana_codegen::VersionedEvent; +pub mod codegen; From 8cea01b4f44157f0f1489319f725fb25f34a2812 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 25 Aug 2021 10:53:49 +0300 Subject: [PATCH 032/104] Correction --- codegen/core/src/es/event/versioned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/core/src/es/event/versioned.rs b/codegen/core/src/es/event/versioned.rs index 39dfba5..efcbe26 100644 --- a/codegen/core/src/es/event/versioned.rs +++ b/codegen/core/src/es/event/versioned.rs @@ -234,7 +234,7 @@ mod spec { assert_eq!( format!("{}", err), - "either `version` or `ver` argument of `#[event]` attribute is \ + "either `ver` or `version` argument of `#[event]` attribute is \ expected to be present, but is absent", ); } From 0a40645eba2eaab003413e1f16bfb804660b3bad Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 25 Aug 2021 14:05:56 +0300 Subject: [PATCH 033/104] Disable -Z minimal-versions for now on CI MSRV check --- .github/workflows/ci.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a01b82c..0f65682 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,11 @@ name: CI -on: [push, pull_request] +on: + push: + branches: ["master"] + tags: ["v*"] + pull_request: + branches: ["master"] env: RUST_BACKTRACE: 1 @@ -104,7 +109,7 @@ jobs: toolchain: ${{ matrix.msrv }} override: true - - run: cargo +nightly update -Z minimal-versions +# - run: cargo +nightly update -Z minimal-versions - run: make test From 8a84f8545acbb9f7dd94f020e3bfb26862a3a6f3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 25 Aug 2021 14:15:01 +0300 Subject: [PATCH 034/104] Some corrections [skip ci] --- codegen/core/Cargo.toml | 19 +- codegen/core/src/es/event/mod.rs | 248 ++++++++++--------------- codegen/core/src/es/event/versioned.rs | 4 +- codegen/core/src/lib.rs | 1 + 4 files changed, 116 insertions(+), 156 deletions(-) diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml index 0cf2e2a..de32c57 100644 --- a/codegen/core/Cargo.toml +++ b/codegen/core/Cargo.toml @@ -13,9 +13,18 @@ homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen/core" repository = "https://github.com/arcana-rs/arcana" readme = "README.md" +[features] +doc = ["arcana-core"] # only for generating documentation + [dependencies] -arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } -proc-macro2 = { version = "1.0", default-features = false } -quote = { version = "1.0", default-features = false } -syn = { version = "1.0", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } -synthez = { version = "0.1", default-features = false } +proc-macro2 = { version = "1.0.4", default-features = false } +quote = { version = "1.0.9", default-features = false } +syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } +synthez = { version = "0.1.2", default-features = false } + +# `doc` feature +arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/core/src/es/event/mod.rs b/codegen/core/src/es/event/mod.rs index 280fb93..fd0314e 100644 --- a/codegen/core/src/es/event/mod.rs +++ b/codegen/core/src/es/event/mod.rs @@ -13,53 +13,46 @@ use synthez::{ParseAttrs, ToTokens}; /// /// # Errors /// -/// - If `input` isn't an `enum`; -/// - If `enum` variant consist not from single event; +/// - If `input` isn't a Rust enum definition; +/// - If some enum variant is not a single-field tuple struct; /// - If failed to parse [`VariantAttrs`]. pub fn derive(input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; - let definitions = Definition::try_from(input)?; + let definition = Definition::try_from(input)?; - Ok(quote! { #definitions }) + Ok(quote! { #definition }) } -/// Attributes for enum variant deriving [`Event`]. -/// -/// [`Event`]: arcana_core::es::Event +/// Helper attributes of `#[derive(Event)]` macro placed on an enum variant. #[derive(Debug, Default, ParseAttrs)] pub struct VariantAttrs { - /// If present, [`Event`] impl and uniqueness check will be skipped for - /// particular enum variant. - /// - /// [`Event`]: arcana_core::es::Event - #[parse(ident, alias = ignore)] - pub skip: Option, + /// Indicator whether to ignore this enum variant for code generation. + #[parse(ident, alias = skip)] + pub ignore: Option, } -/// Definition of [`Event`] derive macro. +/// Representation of an enum implementing [`Event`], used for code generation. /// -/// [`Event`]: arcana_core::es::Event +/// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append(impl_from, unique_event_name_and_ver))] +#[to_tokens(append(impl_event, gen_uniqueness_glue_code))] pub struct Definition { - /// Enum's [`Ident`]. - /// - /// [`Ident`]: struct@syn::Ident + /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, - /// Enum's [`Generics`]. - /// - /// [`Generics`]: syn::Generics + /// [`syn::Generics`] of this Enum's type. pub generics: syn::Generics, - /// Enum's [`Variant`]s alongside with parsed [`VariantAttrs`]. - /// - /// Every [`Variant`] should have exactly 1 [`Field`] in case they are not - /// marked with `#[event(skip)]` attribute. + /// Single-[`Field`] [`Variant`]s of this enum to consider in code + /// generation. /// /// [`Field`]: syn::Field /// [`Variant`]: syn::Variant - pub variants: Vec<(syn::Variant, VariantAttrs)>, + pub variants: Vec, + + /// Indicator whether this enum has variants marked with `#[event(ignore)]` + /// attribute. + pub has_ignored_variants: bool, } impl TryFrom for Definition { @@ -79,104 +72,83 @@ impl TryFrom for Definition { let variants = data .variants .iter() - .map(|variant| { - let attrs = VariantAttrs::parse_attrs("event", variant)?; - - if variant.fields.len() != 1 && attrs.skip.is_none() { - return Err(syn::Error::new( - variant.span(), - "enum variants must have exactly 1 field", - )); - } - - Ok((variant.clone(), attrs)) - }) + .filter_map(|v| Self::parse_variant(v).transpose()) .collect::>()?; + let has_ignored_variants = variants.len() < data.variants.len(); Ok(Self { ident: input.ident, generics: input.generics, variants, + has_ignored_variants, }) } } impl Definition { - /// Generates code to derive [`Event`] by simply matching over every enum - /// variant, which is expected to be itself [`Event`] deriver. + /// Parses and validates [`syn::Variant`] its [`VariantAttrs`]. /// - /// # Panics + /// # Errors /// - /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked - /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. + /// - If [`VariantAttrs`] failed to parse. + /// - If [`syn::Variant`] doesn't have exactly one unnamed 1 [`syn::Field`] + /// and is not ignored. + fn parse_variant( + variant: &syn::Variant, + ) -> syn::Result> { + let attrs = VariantAttrs::parse_attrs("event", variant)?; + if attrs.ignore.is_some() { + return Ok(None); + } + + if variant.fields.len() != 1 { + return Err(syn::Error::new( + variant.span(), + "enum variants must have exactly 1 field", + )); + } + if !matches!(variant.fields, syn::Fields::Unnamed(_)) { + return Err(syn::Error::new( + variant.span(), + "only tuple struct enum variants allowed", + )); + } + + Ok(Some(variant.clone())) + } + + /// Generates code to derive [`Event`][0] trait, by simply matching over + /// each enum variant, which is expected to be itself an [`Event`] + /// implementer. /// /// [`Event`]: arcana_core::es::event::Event - /// [`Field`]: syn::Field - /// [`Variant`]: syn::Variant #[must_use] - pub fn impl_from(&self) -> TokenStream { - let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = - self.generics.split_for_impl(); - let (event_names, event_versions): (TokenStream, TokenStream) = self - .variants - .iter() - .filter_map(|(variant, attrs)| { - if attrs.skip.is_some() { - return None; - } - - let name = &variant.ident; + pub fn impl_event(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let generate_variant = |func: TokenStream| match &variant.fields - { - syn::Fields::Named(named) => { - let field = &named.named.iter().next().unwrap().ident; - quote! { - Self::#name { #field } => { - ::arcana::es::Event::#func(#field) - } - } - } - syn::Fields::Unnamed(_) => { - quote! { - Self::#name(inner) => { - ::arcana::es::Event::#func(inner) - } - } - } - syn::Fields::Unit => unreachable!(), - }; + let var = self.variants.iter().map(|v| &v.ident); - Some(( - generate_variant(quote! { name }), - generate_variant(quote! { version }), - )) - }) - .unzip(); - - let unreachable_for_skip = self - .variants - .iter() - .any(|(_, attr)| attr.skip.is_some()) - .then(|| quote! { _ => unreachable!()}); + let unreachable_arm = self.has_ignored_variants.then(|| { + quote! { + _ => unreachable!(), + } + }); quote! { #[automatically_derived] - impl #impl_generics ::arcana::es::Event for - #name #ty_generics #where_clause - { + impl #impl_gens ::arcana::es::Event for #ty#ty_gens #where_clause { fn name(&self) -> ::arcana::es::event::Name { match self { - #event_names - #unreachable_for_skip + #( Self::#var(f) => ::arcana::es::Event::name(f), )* + #unreachable_arm } } fn version(&self) -> ::arcana::es::event::Version { match self { - #event_versions - #unreachable_for_skip + #( Self::#var(f) => ::arcana::es::Event::version(f), )* + #unreachable_arm } } } @@ -198,54 +170,27 @@ impl Definition { /// [`Field`]: syn::Field /// [`Variant`]: syn::Variant #[must_use] - pub fn unique_event_name_and_ver(&self) -> TokenStream { - let name = &self.ident; - let (impl_generics, ty_generics, where_clause) = - self.generics.split_for_impl(); - let (event_sizes, event_array_population): ( - Vec, - TokenStream, - ) = self - .variants - .iter() - .filter_map(|(variant, attr)| { - attr.skip.is_none().then(|| { - let ty = &variant.fields.iter().next().unwrap().ty; - ( - quote! { - <#ty as ::arcana::codegen::UniqueEvents>::COUNT - }, - quote! {{ - let ev = #ty::__arcana_events(); - let mut local = 0; - while local < ev.len() { - res[global] = ev[local]; - local += 1; - global += 1; - } - }}, - ) - }) - }) - .unzip(); + pub fn gen_uniqueness_glue_code(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let event_sizes = event_sizes - .into_iter() - .fold(None, |acc, size| { - Some(acc.map(|acc| quote! { #acc + #size }).unwrap_or(size)) - }) - .unwrap_or(quote! { 1 }); + let var_ty = + self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); quote! { #[automatically_derived] - impl #impl_generics ::arcana::codegen::UniqueEvents for - #name #ty_generics #where_clause + #[doc(hidden)] + impl #impl_gens ::arcana::codegen::UniqueEvents for #ty#ty_gens + #where_clause { - const COUNT: usize = #event_sizes; + #[doc(hidden)] + const COUNT: usize = + #( <#var_ty as ::arcana::codegen::UniqueEvents>::COUNT )+*; } - impl #impl_generics #name #ty_generics #where_clause { - #[automatically_derived] + #[automatically_derived] + #[doc(hidden)] + impl #impl_gens #ty#ty_gens #where_clause { #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); @@ -253,22 +198,29 @@ impl Definition { ] { let mut res = [ ("", 0); - ::COUNT + ::COUNT, ]; - let mut global = 0; - - #event_array_population + let mut i = 0; + #({ + let events = <#var_ty>::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; + } + })* res } - } - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - #name::__arcana_events() - ) - ); + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + Self::__arcana_events() + ) + ); + } } } } diff --git a/codegen/core/src/es/event/versioned.rs b/codegen/core/src/es/event/versioned.rs index 39dfba5..d026195 100644 --- a/codegen/core/src/es/event/versioned.rs +++ b/codegen/core/src/es/event/versioned.rs @@ -48,9 +48,7 @@ fn parses_as_non_zero_u16(val: &Required) -> syn::Result<()> { #[derive(Debug, ToTokens)] #[to_tokens(append(impl_event_versioned, gen_uniqueness_glue_code))] pub struct Definition { - /// [`Ident`] of this structure's type. - /// - /// [`Ident`]: struct@syn::Ident + /// [`syn::Ident`](struct@syn::Ident) of this structure's type. pub ident: syn::Ident, /// [`syn::Generics`] of this structure's type. diff --git a/codegen/core/src/lib.rs b/codegen/core/src/lib.rs index b846905..7a0aaf8 100644 --- a/codegen/core/src/lib.rs +++ b/codegen/core/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] #![deny( nonstandard_style, rust_2018_idioms, From 2d2c92d75b632a7c3e443c21e184f430ed717e5c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 26 Aug 2021 10:12:12 +0300 Subject: [PATCH 035/104] Try to support generics in internal uniqueness glue code --- codegen/core/src/es/event/mod.rs | 330 ++++++++++++++++++++++--------- examples/event.rs | 4 +- src/es/event.rs | 9 +- 3 files changed, 248 insertions(+), 95 deletions(-) diff --git a/codegen/core/src/es/event/mod.rs b/codegen/core/src/es/event/mod.rs index fd0314e..1788a31 100644 --- a/codegen/core/src/es/event/mod.rs +++ b/codegen/core/src/es/event/mod.rs @@ -73,7 +73,7 @@ impl TryFrom for Definition { .variants .iter() .filter_map(|v| Self::parse_variant(v).transpose()) - .collect::>()?; + .collect::>>()?; let has_ignored_variants = variants.len() < data.variants.len(); Ok(Self { @@ -117,17 +117,79 @@ impl Definition { Ok(Some(variant.clone())) } + /// Replaces [`syn::Type`] generics with default values. + /// + /// - [`syn::Lifetime`] -> `'static`; + /// - [`syn::Type`] -> `()`; + /// - [`syn::Binding`] -> `Ident = ()`; + /// - `Fn(A, B) -> C` -> `Fn((), ()) -> ()`. + fn replace_type_generics_with_default_values(ty: &syn::Type) -> syn::Type { + match ty { + syn::Type::Path(path) => { + let mut path = path.clone(); + + for segment in &mut path.path.segments { + match &mut segment.arguments { + syn::PathArguments::AngleBracketed(generics) => { + for arg in &mut generics.args { + match arg { + syn::GenericArgument::Lifetime(l) => { + *l = syn::parse_quote!('static); + } + syn::GenericArgument::Type(ty) => { + *ty = syn::parse_quote!(()); + } + syn::GenericArgument::Binding(bind) => { + bind.ty = syn::parse_quote!(()); + } + syn::GenericArgument::Const(_) + | syn::GenericArgument::Constraint(_) => {} + } + } + } + syn::PathArguments::Parenthesized(paren) => { + paren.output = syn::parse_quote!(()); + for input in &mut paren.inputs { + *input = syn::parse_quote!(()); + } + } + syn::PathArguments::None => {} + } + } + + syn::Type::Path(path) + } + ty => ty.clone(), + } + } + + /// Replaces [`syn::Generics`] with default values. + /// + /// - [`syn::Lifetime`] -> `'static`; + /// - [`syn::Type`] -> `()`. + fn replace_generics_with_default_values( + generics: &syn::Generics, + ) -> TokenStream { + let generics = generics.params.iter().map(|param| match param { + syn::GenericParam::Lifetime(_) => quote! { 'static }, + syn::GenericParam::Type(_) => quote! { () }, + syn::GenericParam::Const(c) => quote! { #c }, + }); + + quote! { < #( #generics ),* > } + } + /// Generates code to derive [`Event`][0] trait, by simply matching over - /// each enum variant, which is expected to be itself an [`Event`] + /// each enum variant, which is expected to be itself an [`Event`][0] /// implementer. /// - /// [`Event`]: arcana_core::es::event::Event + /// [0]: arcana_core::es::event::Event #[must_use] pub fn impl_event(&self) -> TokenStream { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let var = self.variants.iter().map(|v| &v.ident); + let var = self.variants.iter().map(|v| &v.ident).collect::>(); let unreachable_arm = self.has_ignored_variants.then(|| { quote! { @@ -163,7 +225,7 @@ impl Definition { /// # Panics /// /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked - /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. + /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. /// /// [0]: arcana_core::es::event::Event::name() /// [1]: arcana_core::es::event::Event::version() @@ -174,9 +236,23 @@ impl Definition { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let var_ty = - self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); + let default_generics = + Self::replace_generics_with_default_values(&self.generics); + let (var_ty, var_ty_with_default_generics): (Vec<_>, Vec<_>) = self + .variants + .iter() + .flat_map(|v| &v.fields) + .map(|f| { + ( + &f.ty, + Self::replace_type_generics_with_default_values(&f.ty), + ) + }) + .unzip(); + + // TODO: use `Self::__arcana_events()` inside impl, once + // https://github.com/rust-lang/rust/issues/57775 is resolved. quote! { #[automatically_derived] #[doc(hidden)] @@ -190,7 +266,7 @@ impl Definition { #[automatically_derived] #[doc(hidden)] - impl #impl_gens #ty#ty_gens #where_clause { + impl #ty #default_generics { #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); @@ -198,12 +274,13 @@ impl Definition { ] { let mut res = [ ("", 0); - ::COUNT, + ::COUNT ]; let mut i = 0; #({ - let events = <#var_ty>::__arcana_events(); + let events = + <#var_ty_with_default_generics>::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -214,13 +291,13 @@ impl Definition { res } - - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Self::__arcana_events() - ) - ); } + + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + #ty::#default_generics::__arcana_events() + ) + ); } } } @@ -233,10 +310,8 @@ mod spec { fn derives_enum_impl() { let input = syn::parse_quote! { enum Event { - Event1(EventUnnamed), - Event2 { - event: EventNamed, - } + File(FileEvent), + Chat(ChatEvent), } }; @@ -245,36 +320,118 @@ mod spec { impl ::arcana::es::Event for Event { fn name(&self) -> ::arcana::es::event::Name { match self { - Self::Event1(inner) => { - ::arcana::es::Event::name(inner) - } - Self::Event2 { event } => { - ::arcana::es::Event::name(event) - } + Self::File(f) => ::arcana::es::Event::name(f), + Self::Chat(f) => ::arcana::es::Event::name(f), } } fn version(&self) -> ::arcana::es::event::Version { match self { - Self::Event1(inner) => { - ::arcana::es::Event::version(inner) + Self::File(f) => ::arcana::es::Event::version(f), + Self::Chat(f) => ::arcana::es::Event::version(f), + } + } + } + + #[automatically_derived] + #[doc(hidden)] + impl ::arcana::codegen::UniqueEvents for Event { + #[doc(hidden)] + const COUNT: usize = + ::COUNT + + ::COUNT; + } + + #[automatically_derived] + #[doc(hidden)] + impl Event<> { + #[doc(hidden)] + pub const fn __arcana_events() -> [ + (&'static str, u16); + ::COUNT + ] { + let mut res = [ + ("", 0); + ::COUNT + ]; + + let mut i = 0; + + { + let events = ::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } - Self::Event2 { event } => { - ::arcana::es::Event::version(event) + } + + { + let events = ::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } } + + res + } + } + + ::arcana::codegen::sa::const_assert!( + !::arcana::codegen::unique_events::has_duplicates( + Event::<>::__arcana_events() + ) + ); + }; + + assert_eq!(derive(input).unwrap().to_string(), output.to_string()); + } + + #[test] + fn derives_enum_with_generics_impl() { + let input = syn::parse_quote! { + enum Event<'a, F, C> { + File(FileEvent<'a, F>), + Chat(ChatEvent<'a, C>), + } + }; + + let output = quote! { + #[automatically_derived] + impl<'a, F, C> ::arcana::es::Event for Event<'a, F, C> { + fn name(&self) -> ::arcana::es::event::Name { + match self { + Self::File(f) => ::arcana::es::Event::name(f), + Self::Chat(f) => ::arcana::es::Event::name(f), + } + } + + fn version(&self) -> ::arcana::es::event::Version { + match self { + Self::File(f) => ::arcana::es::Event::version(f), + Self::Chat(f) => ::arcana::es::Event::version(f), + } } } #[automatically_derived] - impl ::arcana::codegen::UniqueEvents for Event { + #[doc(hidden)] + impl<'a, F, C> ::arcana::codegen::UniqueEvents for Event<'a, F, C> { + #[doc(hidden)] const COUNT: usize = - ::COUNT + - ::COUNT; + as ::arcana::codegen::UniqueEvents> + ::COUNT + + as ::arcana::codegen::UniqueEvents> + ::COUNT; } - impl Event { - #[automatically_derived] + #[automatically_derived] + #[doc(hidden)] + impl Event<'static, (), ()> { #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); @@ -285,25 +442,27 @@ mod spec { ::COUNT ]; - let mut global = 0; + let mut i = 0; { - let ev = EventUnnamed::__arcana_events(); - let mut local = 0; - while local < ev.len() { - res[global] = ev[local]; - local += 1; - global += 1; + let events = + < FileEvent<'static, ()> >::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } } { - let ev = EventNamed::__arcana_events(); - let mut local = 0; - while local < ev.len() { - res[global] = ev[local]; - local += 1; - global += 1; + let events = + < ChatEvent<'static, ()> >::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } } @@ -313,7 +472,7 @@ mod spec { ::arcana::codegen::sa::const_assert!( !::arcana::codegen::unique_events::has_duplicates( - Event::__arcana_events() + Event::<'static, (), ()>::__arcana_events() ) ); }; @@ -325,24 +484,18 @@ mod spec { fn skip_unique_check_on_variant() { let input_skip = syn::parse_quote! { enum Event { - Event1(EventUnnamed), - Event2 { - event: EventNamed, - }, + File(FileEvent), + Chat(ChatEvent), #[event(skip)] - #[doc(hidden)] _NonExhaustive } }; let input_ignore = syn::parse_quote! { enum Event { - Event1(EventUnnamed), - Event2 { - event: EventNamed, - }, + File(FileEvent), + Chat(ChatEvent), #[event(ignore)] - #[doc(hidden)] _NonExhaustive } }; @@ -352,38 +505,33 @@ mod spec { impl ::arcana::es::Event for Event { fn name(&self) -> ::arcana::es::event::Name { match self { - Self::Event1(inner) => { - ::arcana::es::Event::name(inner) - } - Self::Event2 { event } => { - ::arcana::es::Event::name(event) - } - _ => unreachable!() + Self::File(f) => ::arcana::es::Event::name(f), + Self::Chat(f) => ::arcana::es::Event::name(f), + _ => unreachable!(), } } fn version(&self) -> ::arcana::es::event::Version { match self { - Self::Event1(inner) => { - ::arcana::es::Event::version(inner) - } - Self::Event2 { event } => { - ::arcana::es::Event::version(event) - } - _ => unreachable!() + Self::File(f) => ::arcana::es::Event::version(f), + Self::Chat(f) => ::arcana::es::Event::version(f), + _ => unreachable!(), } } } #[automatically_derived] + #[doc(hidden)] impl ::arcana::codegen::UniqueEvents for Event { + #[doc(hidden)] const COUNT: usize = - ::COUNT + - ::COUNT; + ::COUNT + + ::COUNT; } - impl Event { - #[automatically_derived] + #[automatically_derived] + #[doc(hidden)] + impl Event<> { #[doc(hidden)] pub const fn __arcana_events() -> [ (&'static str, u16); @@ -394,25 +542,25 @@ mod spec { ::COUNT ]; - let mut global = 0; + let mut i = 0; { - let ev = EventUnnamed::__arcana_events(); - let mut local = 0; - while local < ev.len() { - res[global] = ev[local]; - local += 1; - global += 1; + let events = ::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } } { - let ev = EventNamed::__arcana_events(); - let mut local = 0; - while local < ev.len() { - res[global] = ev[local]; - local += 1; - global += 1; + let events = ::__arcana_events(); + let mut j = 0; + while j < events.len() { + res[i] = events[j]; + j += 1; + i += 1; } } @@ -422,7 +570,7 @@ mod spec { ::arcana::codegen::sa::const_assert!( !::arcana::codegen::unique_events::has_duplicates( - Event::__arcana_events() + Event::<>::__arcana_events() ) ); }; diff --git a/examples/event.rs b/examples/event.rs index 7550336..b289fdd 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -11,13 +11,13 @@ struct FileEvent; #[derive(Event)] enum AnyEvent { Chat(ChatEvent), - File { event: FileEvent }, + File(FileEvent), } fn main() { let ev = AnyEvent::Chat(ChatEvent); assert_eq!(ev.name(), "chat"); - let ev = AnyEvent::File { event: FileEvent }; + let ev = AnyEvent::File(FileEvent); assert_eq!(ev.name(), "file"); } diff --git a/src/es/event.rs b/src/es/event.rs index a6d3cbd..61cbcf5 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -13,6 +13,9 @@ pub use arcana_core::es::event::{ /// underlying [`Event`] or [`Versioned`](trait@Versioned) impls should be /// generated with proc macros. /// +/// __Note__ may not work with complex generics with where clause because of +/// internal code generation. Should be resolved with [#57775][issue]. +/// /// # Attribute arguments /// /// - `#[event(skip)]` — optional @@ -20,6 +23,7 @@ pub use arcana_core::es::event::{ /// Use this value on particular enum variant to skip [`Event`] impl for it /// and check for unique combination of [`Event::name()`][0] and /// [`Event::version()`][1]. +/// /// __Note__: calling [`Event::name()`][0] or [`Event::version()`][1] on those /// variants will result in [`unreachable!()`] panic. /// @@ -39,7 +43,7 @@ pub use arcana_core::es::event::{ /// #[derive(Event)] /// enum AnyEvent { /// Chat(ChatEvent), -/// File { event: FileEvent }, +/// File(FileEvent), /// } /// /// #[derive(Event)] @@ -63,7 +67,7 @@ pub use arcana_core::es::event::{ /// # #[derive(Event)] /// # enum AnyEvent { /// # Chat(ChatEvent), -/// # File { event: FileEvent }, +/// # File(FileEvent), /// # } /// # /// #[derive(Event)] @@ -76,6 +80,7 @@ pub use arcana_core::es::event::{ /// /// [0]: trait@Event::name() /// [1]: trait@Event::version() +/// [issue]: https://github.com/rust-lang/rust/issues/57775 /// [`Event`]: trait@Event #[cfg(feature = "derive")] pub use arcana_codegen::es::event::Event; From 843cae5a0fadb3766b19307e99d3d55919a92471 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 26 Aug 2021 10:16:40 +0300 Subject: [PATCH 036/104] Hide docs dependencies behind the feature --- codegen/Cargo.toml | 7 ++++++- codegen/shim/Cargo.toml | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 3fefdd1..3c20448 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -13,7 +13,12 @@ homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen" repository = "https://github.com/arcana-rs/arcana" readme = "README.md" +[features] +doc = ["arcana-core", "arcana-codegen-shim/doc"] + [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim"} -arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"] } static_assertions = { version = "1.1", default-features = false } + +# `doc` feature +arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index a02a015..54ad2c4 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -16,7 +16,12 @@ readme = "README.md" [lib] proc-macro = true +[features] +doc = ["arcana-core", "arcana-codegen-core/doc"] + [dependencies] arcana-codegen-core = { version = "0.1.0-dev", path = "../core" } -arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"] } syn = { version = "1.0.72", features = ["parsing", "proc-macro"], default-features = false } + +# `doc` feature +arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } From 7685c03e2f901a596408fc63918b42d24fa0616e Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 26 Aug 2021 10:18:53 +0300 Subject: [PATCH 037/104] Corrections --- codegen/Cargo.toml | 2 +- codegen/shim/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 3c20448..bbc677d 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -doc = ["arcana-core", "arcana-codegen-shim/doc"] +doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documentation [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim"} diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 54ad2c4..924997f 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -17,7 +17,7 @@ readme = "README.md" proc-macro = true [features] -doc = ["arcana-core", "arcana-codegen-core/doc"] +doc = ["arcana-codegen-core/doc", "arcana-core"] # only for generating documentation [dependencies] arcana-codegen-core = { version = "0.1.0-dev", path = "../core" } From 549cde477a5dac0e906e96a2c697d6c758e7b70f Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 30 Aug 2021 13:11:00 +0300 Subject: [PATCH 038/104] Implement core Adapter abstractions --- .github/workflows/ci.yml | 2 +- Cargo.toml | 10 + core/Cargo.toml | 2 + core/src/es/adapter/mod.rs | 177 ++++++++++++++ core/src/es/adapter/transformer/mod.rs | 64 +++++ core/src/es/adapter/transformer/strategy.rs | 230 ++++++++++++++++++ core/src/es/mod.rs | 4 + core/src/lib.rs | 1 + examples/event_adapter.rs | 254 ++++++++++++++++++++ src/es/adapter/mod.rs | 6 + src/es/adapter/transformer/mod.rs | 8 + src/es/adapter/transformer/strategy.rs | 6 + src/es/mod.rs | 4 + 13 files changed, 767 insertions(+), 1 deletion(-) create mode 100644 core/src/es/adapter/mod.rs create mode 100644 core/src/es/adapter/transformer/mod.rs create mode 100644 core/src/es/adapter/transformer/strategy.rs create mode 100644 examples/event_adapter.rs create mode 100644 src/es/adapter/mod.rs create mode 100644 src/es/adapter/transformer/mod.rs create mode 100644 src/es/adapter/transformer/strategy.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f65682..96bd4d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: strategy: fail-fast: false matrix: - msrv: [ '1.54.0' ] + msrv: [ '1.56.0' ] os: - ubuntu - macOS diff --git a/Cargo.toml b/Cargo.toml index d0480ad..e24b900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,19 @@ es = ["arcana-core/es"] arcana-core = { version = "0.1.0-dev", path = "./core" } arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } +[dev-dependencies] +derive_more = "0.99" +either = "1" +futures = "0.3" +tokio = { version = "1", features = ["full"] } + [workspace] members = ["codegen", "codegen/core", "codegen/shim", "core"] [[example]] name = "event" required-features = ["derive", "es"] + +[[example]] +name = "event_adapter" +required-features = ["derive", "es"] diff --git a/core/Cargo.toml b/core/Cargo.toml index 8773411..e4bd77f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,4 +18,6 @@ es = [] [dependencies] derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false } +futures = "0.3" +pin-project = "1.0" ref-cast = "1.0" diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs new file mode 100644 index 0000000..ffad6ae --- /dev/null +++ b/core/src/es/adapter/mod.rs @@ -0,0 +1,177 @@ +//! [`Adapter`] definitions. + +pub mod transformer; + +use std::{ + fmt::{Debug, Formatter}, + pin::Pin, + task::{Context, Poll}, +}; + +use futures::{future, stream, Stream, StreamExt as _}; +use pin_project::pin_project; + +#[doc(inline)] +pub use self::transformer::Transformer; + +/// Facility to convert [`Event`]s. +/// Typical use cases include (but are not limited to): +/// +/// - [`Skip`]ping unused [`Event`]s; +/// - Transforming (ex: from one [`Version`] to another); +/// - [`Split`]ting existing [`Event`]s into more granular ones. +/// +/// Provided with blanket impl for [`Transformer`] implementors, so usually you +/// shouldn't implement it manually. +/// +/// [`Event`]: crate::es::Event +/// [`Skip`]: transformer::strategy::Skip +/// [`Split`]: transformer::strategy::Split +/// [`Version`]: crate::es::event::Version +pub trait Adapter { + /// Context for converting [`Event`]s. + /// + /// [`Event`]: crate::es::Event + type Context: ?Sized; + + /// Error of this [`Adapter`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'me, 'ctx>: Stream< + Item = Result, + >; + + /// Converts all incoming [`Event`]s into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform_all<'me, 'ctx>( + &'me self, + events: Events, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx>; +} + +impl Adapter for T +where + Events: Stream, + T: Transformer + 'static, + T::Context: 'static, +{ + type Context = T::Context; + type Error = T::Error; + type Transformed = T::Transformed; + type TransformedStream<'me, 'ctx> = TransformedStream<'me, 'ctx, T, Events>; + + fn transform_all<'me, 'ctx>( + &'me self, + events: Events, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + TransformedStream::new(self, events, context) + } +} + +#[pin_project] +/// [`Stream`] for [`Adapter`] blanket impl. +pub struct TransformedStream<'adapter, 'ctx, Adapter, Events> +where + Events: Stream, + Adapter: Transformer, +{ + #[pin] + events: Events, + #[pin] + transformed_stream: + AdapterTransformedStream<'adapter, 'ctx, Events::Item, Adapter>, + adapter: &'adapter Adapter, + context: &'ctx Adapter::Context, +} + +impl<'adapter, 'ctx, Adapter, Events> Debug + for TransformedStream<'adapter, 'ctx, Adapter, Events> +where + Events: Debug + Stream, + Adapter: Debug + Transformer, + Adapter::Context: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TransformStream") + .field("events", &self.events) + .field("adapter", &self.adapter) + .field("context", &self.context) + .finish_non_exhaustive() + } +} + +type AdapterTransformedStream<'adapter, 'ctx, Event, Adapter> = future::Either< + >::TransformedStream<'adapter, 'ctx>, + stream::Empty< + Result< + >::Transformed, + >::Error, + >, + >, +>; + +impl<'adapter, 'ctx, Adapter, Events> + TransformedStream<'adapter, 'ctx, Adapter, Events> +where + Events: Stream, + Adapter: Transformer, +{ + fn new( + adapter: &'adapter Adapter, + events: Events, + context: &'ctx Adapter::Context, + ) -> Self { + Self { + events, + transformed_stream: stream::empty().right_stream(), + adapter, + context, + } + } +} + +impl<'adapter, 'ctx, Adapter, Events> Stream + for TransformedStream<'adapter, 'ctx, Adapter, Events> +where + Events: Stream, + Adapter: Transformer, +{ + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let mut this = self.project(); + + loop { + let res = + futures::ready!(this.transformed_stream.as_mut().poll_next(cx)); + if let Some(ev) = res { + return Poll::Ready(Some(ev)); + } + + let res = futures::ready!(this.events.as_mut().poll_next(cx)); + if let Some(event) = res { + let new_stream = + Adapter::transform(*this.adapter, event, *this.context); + this.transformed_stream.set(new_stream.left_stream()); + } else { + return Poll::Ready(None); + } + } + } +} diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs new file mode 100644 index 0000000..299652d --- /dev/null +++ b/core/src/es/adapter/transformer/mod.rs @@ -0,0 +1,64 @@ +//! [`Transformer`] definitions. + +pub mod strategy; + +use futures::Stream; + +#[doc(inline)] +pub use strategy::Strategy; + +/// Facility to convert [`Event`]s. +/// Typical use cases include (but are not limited to): +/// +/// - [`Skip`]ping unused [`Event`]s; +/// - Transforming (ex: from one [`Version`] to another); +/// - [`Split`]ting existing [`Event`]s into more granular ones. +/// +/// To reduce boilerplate consider using [`WithStrategy`] with some [`Strategy`] +/// instead of implementing this trait manually. +/// +/// [`Event`]: crate::es::Event +/// [`Skip`]: strategy::Skip +/// [`Split`]: strategy::Split +/// [`Version`]: crate::es::event::Version +pub trait Transformer { + /// Context for converting [`Event`]s. + /// + /// [`Event`]: crate::es::Event + type Context: ?Sized; + + /// Error of this [`Transformer`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'me, 'ctx>: Stream< + Item = Result, + >; + + /// Converts incoming [`Event`] into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform<'me, 'ctx>( + &'me self, + event: Event, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx>; +} + +/// Instead of implementing [`Transformer`] manually, you can use this trait +/// with some [`Strategy`]. +pub trait WithStrategy { + /// [`Strategy`] to transform [`Event`] with. + /// + /// [`Event`]: crate::es::Event + type Strategy; +} diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs new file mode 100644 index 0000000..9a0c95e --- /dev/null +++ b/core/src/es/adapter/transformer/strategy.rs @@ -0,0 +1,230 @@ +//! [`Strategy`] definition and default implementations. + +use std::{ + any::Any, + convert::Infallible, + fmt::{Debug, Formatter}, + iter::Iterator, + marker::PhantomData, +}; + +use futures::{future, stream, Stream, StreamExt as _}; + +use super::{Transformer, WithStrategy}; + +/// Generalized [`Transformer`]. +pub trait Strategy { + /// Context for converting [`Event`]s. + /// + /// [`Event`]: crate::es::Event + type Context: ?Sized; + + /// Error of this [`Strategy`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'me, 'ctx>: Stream< + Item = Result, + >; + + /// Converts incoming [`Event`] into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform<'me, 'ctx>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx>; +} + +impl Transformer for Adapter +where + Adapter: WithStrategy, + Adapter::Strategy: Strategy, +{ + type Context = >::Context; + type Error = >::Error; + type Transformed = + >::Transformed; + type TransformedStream<'me, 'ctx> = >::TransformedStream<'me, 'ctx>; + + fn transform<'me, 'ctx>( + &'me self, + event: Event, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + >::transform( + self, event, context, + ) + } +} + +/// Strategy for skipping [`Event`]s. +/// +/// Until [never] is stabilized, [`Adapter::Transformed`] must implement +/// [`From`] [`Unknown`]. +/// +/// [never]: https://doc.rust-lang.org/stable/std/primitive.never.html +/// [`Adapter::Transformed`]: crate::es::Adapter::Transformed +/// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] +pub struct Skip; + +/// As [`Skip`] [`Strategy`] isn't parametrised by [`Strategy::Transformed`] +/// [`Event`], this type expresses 'never going to be constructed'. +/// +/// [`Event`]: crate::es::Event +// TODO: replace with `never`(`!`), once it's stabilized. +#[derive(Clone, Copy, Debug)] +pub enum Unknown {} + +impl Strategy for Skip { + type Context = dyn Any; + type Error = Infallible; + type Transformed = Unknown; + type TransformedStream<'me, 'ctx> = + stream::Empty>; + + fn transform<'me, 'ctx>( + _: &'me Adapter, + _: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + stream::empty() + } +} + +/// Just passes [`Event`] as is, without any conversions. +/// +/// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] +pub struct AsIs; + +impl Strategy for AsIs { + type Context = dyn Any; + type Error = Infallible; + type Transformed = Event; + type TransformedStream<'me, 'ctx> = + stream::Once>>; + + fn transform<'me, 'ctx>( + _: &'me Adapter, + event: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + stream::once(future::ready(Ok(event))) + } +} + +/// Converts [`Event`] using [`From`] impl. +/// +/// [`Event`]: crate::es::Event +pub struct Into(PhantomData); + +impl Clone for Into { + fn clone(&self) -> Self { + Self(PhantomData) + } +} + +impl Copy for Into {} + +impl Debug for Into { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Into").finish() + } +} + +impl Strategy for Into +where + IntoEvent: From + 'static, +{ + type Context = dyn Any; + type Error = Infallible; + type Transformed = IntoEvent; + type TransformedStream<'me, 'ctx> = + stream::Once>>; + + fn transform<'me, 'ctx>( + _: &'me Adapter, + event: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + stream::once(future::ready(Ok(IntoEvent::from(event)))) + } +} + +/// [`Strategy`] for splitting single [`Event`] into multiple. Implement +/// [`Splitter`] to define splitting logic. +/// +/// [`Event`]: crate::es::Event +pub struct Split(PhantomData); + +/// Split single [`Event`] into multiple for [`Split`] [`Strategy`]. +/// +/// [`Event`]: crate::es::Event +pub trait Splitter { + /// [`Iterator`] of split [`Event`]s. + /// + /// [`Event`]: crate::es::Event + type Iterator: Iterator; + + /// Splits [`Event`]. + /// + /// [`Event`]: crate::es::Event + fn split(&self, event: From) -> Self::Iterator; +} + +impl Clone for Split { + fn clone(&self) -> Self { + Self(PhantomData) + } +} + +impl Copy for Split {} + +impl Debug for Split { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Into").finish() + } +} + +impl Strategy for Split +where + Adapter: Splitter, +{ + type Context = dyn Any; + type Error = Infallible; + type Transformed = ::Item; + type TransformedStream<'me, 'ctx> = SplitStream; + + fn transform<'me, 'ctx>( + adapter: &'me Adapter, + event: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + stream::iter(adapter.split(event)).map(Ok) + } +} + +type SplitStream = stream::Map< + stream::Iter<>::Iterator>, + fn( + <>::Iterator as Iterator>::Item, + ) -> Result< + <>::Iterator as Iterator>::Item, + Infallible, + >, +>; diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 16464f5..47f0bae 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -2,8 +2,12 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html +pub mod adapter; pub mod event; +#[doc(inline)] +pub use self::adapter::Adapter; + #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, diff --git a/core/src/lib.rs b/core/src/lib.rs index 14848ce..8fcb5aa 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(generic_associated_types)] #![doc = include_str!("../README.md")] #![deny( nonstandard_style, diff --git a/examples/event_adapter.rs b/examples/event_adapter.rs new file mode 100644 index 0000000..12470cc --- /dev/null +++ b/examples/event_adapter.rs @@ -0,0 +1,254 @@ +#![feature(generic_associated_types)] + +use std::{any::Any, array, convert::Infallible}; + +use arcana::es::{ + adapter::{ + transformer::{self, strategy}, + Transformer, + }, + Adapter as _, +}; +use derive_more::From; +use either::Either; +use futures::{future, stream, StreamExt as _, TryStreamExt as _}; + +#[tokio::main] +async fn main() { + let ctx = 1_usize; // Can be any type in this example. + let events = stream::iter::<[InputEmailEvents; 5]>([ + EmailConfirmed { + confirmed_by: "1".to_string(), + } + .into(), + EmailAdded { + email: "2".to_string(), + } + .into(), + EmailAddedAndConfirmed { + email: "3".to_string(), + confirmed_by: Some("3".to_string()), + } + .into(), + EmailAddedAndConfirmed { + email: "4".to_string(), + confirmed_by: None, + } + .into(), + EmailConfirmationSent.into(), + ]); + + let collect = Adapter + .transform_all(events, &ctx) + .try_collect::>() + .await + .unwrap(); + + println!("context: {}\nevents:{:#?}", ctx, collect); +} + +#[derive(Debug)] +struct EmailConfirmationSent; + +#[derive(Debug)] +struct EmailAddedAndConfirmed { + email: String, + confirmed_by: Option, +} + +#[derive(Debug)] +struct EmailAdded { + email: String, +} + +#[derive(Debug)] +struct EmailConfirmed { + confirmed_by: String, +} + +#[derive(Debug, From)] +enum InputEmailEvents { + ConfirmationSent(EmailConfirmationSent), + AddedAndConfirmed(EmailAddedAndConfirmed), + Added(EmailAdded), + Confirmed(EmailConfirmed), +} + +#[derive(Debug, From)] +enum EmailAddedOrConfirmed { + Added(EmailAdded), + Confirmed(EmailConfirmed), +} + +struct Adapter; + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::AsIs; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Into; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Split>; +} + +impl + strategy::Splitter< + EmailAddedAndConfirmed, + Either, + > for Adapter +{ + type Iterator = SplitEmail; + + fn split(&self, event: EmailAddedAndConfirmed) -> Self::Iterator { + use either::{Left, Right}; + + #[allow(clippy::option_if_let_else)] // use of moved value + if let Some(confirmed_by) = event.confirmed_by { + Right(array::IntoIter::new([ + Left(EmailAdded { email: event.email }), + Right(EmailConfirmed { confirmed_by }), + ])) + } else { + Left(array::IntoIter::new([Left(EmailAdded { + email: event.email, + })])) + } + } +} + +type SplitEmail = Either< + array::IntoIter, 1>, + array::IntoIter, 2>, +>; + +impl From> for EmailAddedOrConfirmed { + fn from(val: Either) -> Self { + match val { + Either::Left(added) => EmailAddedOrConfirmed::Added(added), + Either::Right(conf) => EmailAddedOrConfirmed::Confirmed(conf), + } + } +} + +// TODO: generate + +impl From for EmailAddedOrConfirmed { + fn from(u: strategy::Unknown) -> Self { + match u {} + } +} + +macro_rules! transformed_stream { + ( + $me: lifetime, + $ctx: lifetime, + $adapter: ty, + $from: ty, + $event: ty + $(,)? + ) => { + IntoTransformedStream<$me, $ctx, $adapter, $event, $from> + }; + ( + $me: lifetime, + $ctx: lifetime, + $adapter: ty, + $from: ty, + $event: ty, + $( $event_tail: ty ),+ + $(,)? + ) => { + future::Either< + IntoTransformedStream<$me, $ctx, $adapter, $event, $from>, + transformed_stream!($me, $ctx, $adapter, $from, $( $event_tail ),+) + > + }; +} + +type IntoTransformedStream<'me, 'ctx, Adapter, Event, From> = stream::Map< + >::TransformedStream<'me, 'ctx>, + fn( + Result< + >::Transformed, + >::Error, + >, + ) -> Result< + >::Transformed, + >::Error, + >, +>; + +impl Transformer for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = EmailAddedOrConfirmed; + type TransformedStream<'me, 'ctx> = transformed_stream!( + 'me, + 'ctx, + Adapter, + InputEmailEvents, + EmailConfirmationSent, + EmailAddedAndConfirmed, + EmailAdded, + EmailConfirmed, + ); + + fn transform<'me, 'ctx>( + &'me self, + event: InputEmailEvents, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + fn transform_result( + res: Result, + ) -> Result + where + IntoOk: From, + IntoErr: From, + { + res.map(Into::into).map_err(Into::into) + } + + match event { + InputEmailEvents::ConfirmationSent(event) => { + >::transform( + self, event, context, + ) + .map(transform_result as fn(_) -> _) + .left_stream() + } + InputEmailEvents::AddedAndConfirmed(event) => { + >::transform( + self, event, context, + ) + .map(transform_result as fn(_) -> _) + .left_stream() + .right_stream() + } + InputEmailEvents::Added(event) => { + >::transform( + self, event, context, + ) + .map(transform_result as fn(_) -> _) + .left_stream() + .right_stream() + .right_stream() + } + InputEmailEvents::Confirmed(event) => { + >::transform( + self, event, context, + ) + .map(transform_result as fn(_) -> _) + .right_stream() + .right_stream() + .right_stream() + } + } + } +} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs new file mode 100644 index 0000000..e3e7ede --- /dev/null +++ b/src/es/adapter/mod.rs @@ -0,0 +1,6 @@ +//! [`Adapter`] definitions. + +pub mod transformer; + +#[doc(inline)] +pub use arcana_core::es::adapter::{Adapter, TransformedStream, Transformer}; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs new file mode 100644 index 0000000..e1c2900 --- /dev/null +++ b/src/es/adapter/transformer/mod.rs @@ -0,0 +1,8 @@ +//! [`Transformer`] definitions. + +pub mod strategy; + +#[doc(inline)] +pub use arcana_core::es::adapter::transformer::{ + Strategy, Transformer, WithStrategy, +}; diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs new file mode 100644 index 0000000..d8bf17b --- /dev/null +++ b/src/es/adapter/transformer/strategy.rs @@ -0,0 +1,6 @@ +//! [`Strategy`] definition and default implementations. + +#[doc(inline)] +pub use arcana_core::es::adapter::transformer::strategy::{ + AsIs, Into, Skip, Split, Splitter, Strategy, Unknown, +}; diff --git a/src/es/mod.rs b/src/es/mod.rs index 16464f5..47f0bae 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -2,8 +2,12 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html +pub mod adapter; pub mod event; +#[doc(inline)] +pub use self::adapter::Adapter; + #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, From a9e9fa105e559ff162de8bfee125f3c542e0b410 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 31 Aug 2021 09:26:38 +0300 Subject: [PATCH 039/104] WIP --- Cargo.toml | 2 +- codegen/core/Cargo.toml | 2 +- codegen/core/src/es/adapter.rs | 261 +++++++++++++++++++++++++++++++++ codegen/core/src/es/mod.rs | 1 + codegen/shim/src/lib.rs | 10 ++ codegen/src/es/adapter.rs | 1 + codegen/src/es/mod.rs | 1 + examples/event_adapter.rs | 140 +++--------------- src/codegen.rs | 2 + src/es/adapter/mod.rs | 2 + src/es/mod.rs | 4 +- 11 files changed, 300 insertions(+), 126 deletions(-) create mode 100644 codegen/core/src/es/adapter.rs create mode 100644 codegen/src/es/adapter.rs diff --git a/Cargo.toml b/Cargo.toml index e24b900..d86569b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,11 @@ es = ["arcana-core/es"] [dependencies] arcana-core = { version = "0.1.0-dev", path = "./core" } arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } +futures = "0.3" [dev-dependencies] derive_more = "0.99" either = "1" -futures = "0.3" tokio = { version = "1", features = ["full"] } [workspace] diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml index de32c57..432e61e 100644 --- a/codegen/core/Cargo.toml +++ b/codegen/core/Cargo.toml @@ -20,7 +20,7 @@ doc = ["arcana-core"] # only for generating documentation proc-macro2 = { version = "1.0.4", default-features = false } quote = { version = "1.0.9", default-features = false } syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } -synthez = { version = "0.1.2", default-features = false } +synthez = { version = "0.1.3", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } diff --git a/codegen/core/src/es/adapter.rs b/codegen/core/src/es/adapter.rs new file mode 100644 index 0000000..e7e7805 --- /dev/null +++ b/codegen/core/src/es/adapter.rs @@ -0,0 +1,261 @@ +//! `#[derive(adapter::Transformer)]` macro implementation. + +use std::convert::TryFrom; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use synthez::{ParseAttrs, Required, Spanning, ToTokens}; + +pub fn derive(input: TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + let definition = Definition::try_from(input)?; + + Ok(quote! { #definition }) +} + +/// Helper attributes of `#[derive(adapter::Transformer)]` macro. +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { + #[parse(nested)] + pub transformer: Required>, +} + +#[derive(Debug, Default, ParseAttrs)] +pub struct TransformerAttrs { + /// Type to derive [`adapter::Transformer`][0] on. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[parse(value)] + pub adapter: Required, + + /// [`adapter::Transformer::Transformed`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Transformed + #[parse(value, alias = into)] + pub transformed: Required, + + /// [`adapter::Transformer::Context`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Context + #[parse(value, alias = ctx)] + pub context: Required, + + /// [`adapter::Transformer::Error`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Error + #[parse(value, alias = err)] + pub error: Required, +} + +#[derive(Debug, ToTokens)] +#[to_tokens(append(derive_transformer, from_unknown))] +pub struct Definition { + pub ident: syn::Ident, + pub generics: syn::Generics, + pub variants: Vec, + pub adapter: syn::TypePath, + pub transformed: syn::TypePath, + pub context: syn::Type, + pub error: syn::TypePath, +} + +impl TryFrom for Definition { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> syn::Result { + let attrs = Attrs::parse_attrs("event", &input)?; + let TransformerAttrs { + adapter, + transformed, + context, + error, + } = attrs.transformer.into_inner().into_inner(); + + let data = if let syn::Data::Enum(data) = input.data { + data + } else { + return Err(syn::Error::new(input.span(), "expected enum only")); + }; + + Ok(Self { + ident: input.ident, + generics: input.generics, + variants: data.variants.into_iter().collect(), + adapter: adapter.into_inner(), + transformed: transformed.into_inner(), + context: context.into_inner(), + error: error.into_inner(), + }) + } +} + +impl Definition { + fn derive_transformer(&self) -> TokenStream { + let event = &self.ident; + let adapter = &self.adapter; + let context = &self.context; + let error = &self.error; + let transformed = &self.transformed; + let inner_match = self.inner_match(); + let transformed_stream = self.transformed_stream(); + + quote! { + impl ::arcana::es::adapter::Transformer<#event> for #adapter { + type Context = #context; + type Error = #error; + type Transformed = #transformed; + type TransformedStream<'me, 'ctx> = #transformed_stream; + + fn transform<'me, 'ctx>( + &'me self, + event: #event, + context: &'ctx >::Context, + ) -> >:: + TransformedStream<'me, 'ctx> + { + use ::arcana::codegen::futures::StreamExt as _; + + fn transform_result( + res: Result, + ) -> Result + where + IntoOk: From, + IntoErr: From, + { + ::std::result::Result::map_err( + ::std::result::Result::map( + res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + } + + match event { + #inner_match + } + } + } + } + } + + fn transformed_stream(&self) -> TokenStream { + let adapter = &self.adapter; + let from = &self.ident; + + let stream = |ev: TokenStream| quote! { + ::arcana::codegen::futures::stream::Map< + <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: + TransformedStream<'me, 'ctx>, + fn( + Result< + <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: + Transformed, + <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: + Error, + >, + ) -> Result< + <#adapter as ::arcana::es::adapter::Transformer<#from>>:: + Transformed, + <#adapter as ::arcana::es::adapter::Transformer<#from>>:: + Error, + >, + > + }; + + let last_variant= &self + .variants + .last() + .unwrap() + .fields + .iter() + .next() + .unwrap() + .ty; + let last_variant = stream(last_variant.into_token_stream()); + + self + .variants + .iter() + .map(|var| &var.fields.iter().next().unwrap().ty) + .rev() + .skip(1) + .fold(last_variant, |ty, variant| { + let variant = stream(variant.into_token_stream()); + quote! { + ::arcana::codegen::futures::future::Either< + #variant, + #ty, + > + } + }) + } + + fn inner_match(&self) -> TokenStream { + let event = &self.ident; + let adapter = &self.adapter; + + let variant = &self.variants.first().unwrap().ident; + let variant_val = &self + .variants + .first() + .unwrap() + .fields + .iter() + .next() + .unwrap() + .ty; + + let matcher = |variant: TokenStream, variant_val: TokenStream, ext: TokenStream| { + quote! { + #event::#variant(event) => { + <#adapter as ::arcana::es::adapter::Transformer< + #variant_val + >>::transform(self, event, context) + .map(transform_result as fn(_) -> _) + #ext + }, + } + }; + + if self.variants.len() == 1 { + return matcher(variant.into_token_stream(), variant_val.into_token_stream(), quote! {}); + } + + self.variants + .iter() + .enumerate() + .map(|(i, var)| { + let variant = &var.ident; + let variant_val = &var.fields.iter().next().unwrap().ty; + + let left_stream = + (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); + let convert = std::iter::repeat(quote! { .left_stream() }) + .take(left_stream) + .chain( + std::iter::repeat(quote! { .right_stream() }).take(i), + ) + .collect(); + matcher(variant.into_token_stream(), variant_val.into_token_stream(), convert) + }) + .collect() + } + + fn from_unknown(&self) -> TokenStream { + let transformed = &self.transformed; + quote! { + impl From<::arcana::es::adapter::transformer::strategy::Unknown> + for #transformed + { + fn from( + u: ::arcana::es::adapter::transformer::strategy::Unknown, + ) -> Self { + match u {} + } + } + } + } +} diff --git a/codegen/core/src/es/mod.rs b/codegen/core/src/es/mod.rs index 0234461..3ef94f1 100644 --- a/codegen/core/src/es/mod.rs +++ b/codegen/core/src/es/mod.rs @@ -3,3 +3,4 @@ //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub mod event; +pub mod adapter; diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index b8f7b00..209da50 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -42,3 +42,13 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +/// Macro for deriving [`event::Versioned`]. +/// +/// [`event::Versioned`]: arcana_core::es::event::Versioned +#[proc_macro_derive(EventTransformer, attributes(event))] +pub fn derive_event_transformer(input: TokenStream) -> TokenStream { + codegen::es::adapter::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/codegen/src/es/adapter.rs b/codegen/src/es/adapter.rs new file mode 100644 index 0000000..dd65fc3 --- /dev/null +++ b/codegen/src/es/adapter.rs @@ -0,0 +1 @@ +pub use arcana_codegen_shim::EventTransformer; diff --git a/codegen/src/es/mod.rs b/codegen/src/es/mod.rs index a806fb9..1a19f28 100644 --- a/codegen/src/es/mod.rs +++ b/codegen/src/es/mod.rs @@ -2,4 +2,5 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html +pub mod adapter; pub mod event; diff --git a/examples/event_adapter.rs b/examples/event_adapter.rs index 12470cc..bce3c13 100644 --- a/examples/event_adapter.rs +++ b/examples/event_adapter.rs @@ -3,15 +3,12 @@ use std::{any::Any, array, convert::Infallible}; use arcana::es::{ - adapter::{ - transformer::{self, strategy}, - Transformer, - }, - Adapter as _, + adapter::transformer::{self, strategy}, + EventAdapter as _, EventTransformer, }; use derive_more::From; use either::Either; -use futures::{future, stream, StreamExt as _, TryStreamExt as _}; +use futures::{stream, TryStreamExt as _}; #[tokio::main] async fn main() { @@ -47,6 +44,8 @@ async fn main() { println!("context: {}\nevents:{:#?}", ctx, collect); } +// Events definitions + #[derive(Debug)] struct EmailConfirmationSent; @@ -66,7 +65,15 @@ struct EmailConfirmed { confirmed_by: String, } -#[derive(Debug, From)] +#[derive(Debug, From, EventTransformer)] +#[event( + transformer( + adapter = Adapter, + into = EmailAddedOrConfirmed, + context = dyn Any, + err = Infallible, + ) +)] enum InputEmailEvents { ConfirmationSent(EmailConfirmationSent), AddedAndConfirmed(EmailAddedAndConfirmed), @@ -80,6 +87,8 @@ enum EmailAddedOrConfirmed { Confirmed(EmailConfirmed), } +// Adapter implementations + struct Adapter; impl transformer::WithStrategy for Adapter { @@ -91,6 +100,7 @@ impl transformer::WithStrategy for Adapter { } impl transformer::WithStrategy for Adapter { + // In this case can also be strategy::AsIs. type Strategy = strategy::Into; } @@ -136,119 +146,3 @@ impl From> for EmailAddedOrConfirmed { } } } - -// TODO: generate - -impl From for EmailAddedOrConfirmed { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -macro_rules! transformed_stream { - ( - $me: lifetime, - $ctx: lifetime, - $adapter: ty, - $from: ty, - $event: ty - $(,)? - ) => { - IntoTransformedStream<$me, $ctx, $adapter, $event, $from> - }; - ( - $me: lifetime, - $ctx: lifetime, - $adapter: ty, - $from: ty, - $event: ty, - $( $event_tail: ty ),+ - $(,)? - ) => { - future::Either< - IntoTransformedStream<$me, $ctx, $adapter, $event, $from>, - transformed_stream!($me, $ctx, $adapter, $from, $( $event_tail ),+) - > - }; -} - -type IntoTransformedStream<'me, 'ctx, Adapter, Event, From> = stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - Result< - >::Transformed, - >::Error, - >, - ) -> Result< - >::Transformed, - >::Error, - >, ->; - -impl Transformer for Adapter { - type Context = dyn Any; - type Error = Infallible; - type Transformed = EmailAddedOrConfirmed; - type TransformedStream<'me, 'ctx> = transformed_stream!( - 'me, - 'ctx, - Adapter, - InputEmailEvents, - EmailConfirmationSent, - EmailAddedAndConfirmed, - EmailAdded, - EmailConfirmed, - ); - - fn transform<'me, 'ctx>( - &'me self, - event: InputEmailEvents, - context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { - fn transform_result( - res: Result, - ) -> Result - where - IntoOk: From, - IntoErr: From, - { - res.map(Into::into).map_err(Into::into) - } - - match event { - InputEmailEvents::ConfirmationSent(event) => { - >::transform( - self, event, context, - ) - .map(transform_result as fn(_) -> _) - .left_stream() - } - InputEmailEvents::AddedAndConfirmed(event) => { - >::transform( - self, event, context, - ) - .map(transform_result as fn(_) -> _) - .left_stream() - .right_stream() - } - InputEmailEvents::Added(event) => { - >::transform( - self, event, context, - ) - .map(transform_result as fn(_) -> _) - .left_stream() - .right_stream() - .right_stream() - } - InputEmailEvents::Confirmed(event) => { - >::transform( - self, event, context, - ) - .map(transform_result as fn(_) -> _) - .right_stream() - .right_stream() - .right_stream() - } - } - } -} diff --git a/src/codegen.rs b/src/codegen.rs index e77ff32..519ca0a 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -4,3 +4,5 @@ pub use arcana_codegen::{ sa, unique_events::{self, UniqueEvents}, }; + +pub use futures; diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index e3e7ede..7a265c4 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -4,3 +4,5 @@ pub mod transformer; #[doc(inline)] pub use arcana_core::es::adapter::{Adapter, TransformedStream, Transformer}; + +pub use arcana_codegen::es::adapter::EventTransformer as Transformer; diff --git a/src/es/mod.rs b/src/es/mod.rs index 47f0bae..b1555ad 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -6,7 +6,9 @@ pub mod adapter; pub mod event; #[doc(inline)] -pub use self::adapter::Adapter; +pub use self::adapter::{ + Adapter as EventAdapter, Transformer as EventTransformer, +}; #[doc(inline)] pub use self::event::{ From 5341cbfe90afc09b6152d7c6f38ee53c66d6095e Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 31 Aug 2021 10:24:50 +0300 Subject: [PATCH 040/104] Check uniqueness of event::Name and event::Version only on different Rust types. --- codegen/impl/src/es/event/mod.rs | 24 +++++++------- codegen/impl/src/es/event/versioned.rs | 35 +++++++++++++++++--- codegen/shim/src/lib.rs | 45 +++++++++++++------------- codegen/src/unique_events.rs | 34 ++++++++++++------- 4 files changed, 88 insertions(+), 50 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index f807461..594aa80 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -176,9 +176,9 @@ impl Definition { } } - /// Generates hidden machinery code used to statically check uniqueness of - /// all the [`Event::name`][0]s and [`Event::version`][1]s pairs imposed by - /// enum variants. + /// Generates hidden machinery code used to statically check that all the + /// [`Event::name`][0]s and [`Event::version`][1]s pairs are corresponding + /// to single Rust type. /// /// # Panics /// @@ -222,11 +222,11 @@ impl Definition { impl #ty#ty_gens { #[doc(hidden)] pub const fn __arcana_events() -> [ - (&'static str, u16); + (&'static str, &'static str, u16); ::COUNT ] { let mut res = [ - ("", 0); + ("", "", 0); ::COUNT ]; @@ -300,11 +300,11 @@ mod spec { impl Event { #[doc(hidden)] pub const fn __arcana_events() -> [ - (&'static str, u16); + (&'static str, &'static str, u16); ::COUNT ] { let mut res = [ - ("", 0); + ("", "", 0); ::COUNT ]; @@ -388,11 +388,11 @@ mod spec { impl Event<'a, F, C> { #[doc(hidden)] pub const fn __arcana_events() -> [ - (&'static str, u16); + (&'static str, &'static str, u16); ::COUNT ] { let mut res = [ - ("", 0); + ("", "", 0); ::COUNT ]; @@ -486,11 +486,11 @@ mod spec { impl Event { #[doc(hidden)] pub const fn __arcana_events() -> [ - (&'static str, u16); + (&'static str, &'static str, u16); ::COUNT ] { let mut res = [ - ("", 0); + ("", "", 0); ::COUNT ]; @@ -546,7 +546,7 @@ mod spec { let err = super::derive(input).unwrap_err(); - assert_eq!(err.to_string(), "enum variants must have exactly 1 field",); + assert_eq!(err.to_string(), "enum variants must have exactly 1 field"); } #[test] diff --git a/codegen/impl/src/es/event/versioned.rs b/codegen/impl/src/es/event/versioned.rs index 0e95eba..ab57eb0 100644 --- a/codegen/impl/src/es/event/versioned.rs +++ b/codegen/impl/src/es/event/versioned.rs @@ -125,6 +125,9 @@ impl Definition { /// /// [`Event::name`]: arcana_core::es::Event::name /// [`Event::version`]: arcana_core::es::Event::version + // TODO: replace `::std::concat!(...)` with `TypeId::of()` once it gets + // constified. + // https://github.com/rust-lang/rust/issues/77125 #[must_use] pub fn gen_uniqueness_glue_code(&self) -> TokenStream { let ty = &self.ident; @@ -147,8 +150,20 @@ impl Definition { impl #impl_gens #ty#ty_gens #where_clause { #[doc(hidden)] #[inline] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [(#event_name, #event_ver)] + pub const fn __arcana_events() -> + [(&'static str, &'static str, u16); 1] + { + [( + ::std::concat!( + ::std::file!(), + "_", + ::std::line!(), + "_", + ::std::column!(), + ), + #event_name, + #event_ver, + )] } } } @@ -192,8 +207,20 @@ mod spec { impl Event { #[doc(hidden)] #[inline] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [("event", 1)] + pub const fn __arcana_events() -> + [(&'static str, &'static str, u16); 1] + { + [( + ::std::concat!( + ::std::file!(), + "_", + ::std::line!(), + "_", + ::std::column!(), + ), + "event", + 1, + )] } } }; diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 0988bc1..9d285dd 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -31,8 +31,9 @@ use proc_macro::TokenStream; /// For structs consider using [`#[derive(Versioned)]`](macro@VersionedEvent). /// /// This macro ensures that every combination of [`Event::name`][0] and -/// [`Event::version`][1] are unique. The only limitation is that all the -/// underlying [`Event`] or [`Versioned`] impls should be derived too. +/// [`Event::version`][1] is corresponding to single Rust type. The only +/// limitation is that all the underlying [`Event`] or [`Versioned`] impls +/// should be derived too. /// /// > __WARNING:__ Currently may not work with complex generics using where /// > clause because of `const` evaluation limitations. Should be @@ -60,20 +61,15 @@ use proc_macro::TokenStream; /// struct ChatEvent; /// /// #[derive(event::Versioned)] -/// #[event(name = "file", version = 1)] -/// struct FileEvent; +/// #[event(name = "chat", version = 1)] +/// struct AnotherChatEvent; /// +/// // This fails to compile as contains different Rust types with same +/// // `event::Name` and `event::Version`. /// #[derive(Event)] /// enum AnyEvent { /// Chat(ChatEvent), -/// File(FileEvent), -/// } -/// -/// // This fails to compile as contains `FileEvent` duplicated. -/// #[derive(Event)] -/// enum DuplicatedEvent { -/// Any(AnyEvent), -/// File(FileEvent), +/// AnotherChat(AnotherChatEvent), /// } /// ``` /// @@ -85,20 +81,23 @@ use proc_macro::TokenStream; /// # struct ChatEvent; /// # /// # #[derive(event::Versioned)] -/// # #[event(name = "file", version = 1)] -/// # struct FileEvent; -/// # -/// # #[derive(Event)] -/// # enum AnyEvent { -/// # Chat(ChatEvent), -/// # File(FileEvent), -/// # } +/// # #[event(name = "chat", version = 1)] +/// # struct AnotherChatEvent; /// # /// #[derive(Event)] -/// enum DuplicatedEvent { -/// Any(AnyEvent), +/// enum AnyEvent { +/// Chat(ChatEvent), /// #[event(ignore)] -/// File(FileEvent), +/// AnotherChat(AnotherChatEvent), +/// } +/// +/// // This example doesn't need `#[event(ignore)]` attribute, as every +/// // combination of `event::Name` and `event::Version` correspond to single +/// // Rust type. +/// #[derive(Event)] +/// enum MoreEvents { +/// Chat(ChatEvent), +/// ChatOnceAgain(ChatEvent), /// } /// ``` /// diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index 2007bdc..ec55326 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -1,13 +1,16 @@ -//! Utils for ensuring in compile time that every [`Event`] variant has a unique -//! combination of [`Event::name`] and [`Event::version`]. +//! Utils for ensuring in compile time that every [`Event`] variant's +//! combination of [`Event::name`] and [`Event::version`] is corresponding to +//! single Rust type. //! //! # Explanation //! //! Main idea is that every [`Event`] or [`event::Versioned`] deriving generates -//! a hidden `const fn __arcana_events() -> [(&'static str, u16); size]` method. -//! This array consists of [`event::Name`]s and [`event::Version`]s of all the -//! [`Event`] variants. Uniqueness is checked then with [`const_assert`]ing the -//! [`has_duplicates()`] function. +//! a hidden +//! `const fn __arcana_events() -> [(&'static str, &'static str, u16); size]` +//! method. +//! This array consists of unique Rust type identifiers, [`event::Name`]s and +//! [`event::Version`]s of all the [`Event`] variants. Correctness is checked +//! then with [`const_assert`]ing the [`has_duplicates()`] function. //! //! [`const_assert`]: static_assertions::const_assert //! [`Event`]: arcana_core::es::Event @@ -28,16 +31,25 @@ pub trait UniqueEvents { const COUNT: usize; } -/// Checks whether the given array of `events` combinations has duplicates. +/// Checks whether the given array of `events` combinations of [`Event::name`] +/// and [`Event::version`] corresponding to different Rust types. +/// +/// [`Event::name`]: arcana_core::es::Event::name +/// [`Event::version`]: arcana_core::es::Event::version #[must_use] -pub const fn has_duplicates(events: [(&str, u16); N]) -> bool { +pub const fn has_duplicates( + events: [(&str, &str, u16); N], +) -> bool { let mut outer = 0; while outer < events.len() { let mut inner = outer + 1; while inner < events.len() { - let (inner_name, inner_ver) = events[inner]; - let (outer_name, outer_ver) = events[outer]; - if str_eq(inner_name, outer_name) && inner_ver == outer_ver { + let (inner_ty, inner_name, inner_ver) = events[inner]; + let (outer_ty, outer_name, outer_ver) = events[outer]; + if !str_eq(inner_ty, outer_ty) + && str_eq(inner_name, outer_name) + && inner_ver == outer_ver + { return true; } inner += 1; From 2dde2631a2340a9dc6979ae4ddacd63b1998dc97 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 31 Aug 2021 15:46:25 +0300 Subject: [PATCH 041/104] WIP [skip ci] --- codegen/core/Cargo.toml | 3 +- codegen/core/src/es/adapter.rs | 261 ---------------- codegen/core/src/es/event/mod.rs | 1 + codegen/core/src/es/event/transformer.rs | 380 +++++++++++++++++++++++ codegen/core/src/es/mod.rs | 1 - codegen/shim/src/lib.rs | 2 +- codegen/src/es/adapter.rs | 4 + examples/event_adapter.rs | 61 +++- 8 files changed, 445 insertions(+), 268 deletions(-) delete mode 100644 codegen/core/src/es/adapter.rs create mode 100644 codegen/core/src/es/event/transformer.rs diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml index 432e61e..c39c362 100644 --- a/codegen/core/Cargo.toml +++ b/codegen/core/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -doc = ["arcana-core"] # only for generating documentation +doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] proc-macro2 = { version = "1.0.4", default-features = false } @@ -24,6 +24,7 @@ synthez = { version = "0.1.3", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } +futures = { version = "0.3", default-features = false, optional = true } [package.metadata.docs.rs] all-features = true diff --git a/codegen/core/src/es/adapter.rs b/codegen/core/src/es/adapter.rs deleted file mode 100644 index e7e7805..0000000 --- a/codegen/core/src/es/adapter.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! `#[derive(adapter::Transformer)]` macro implementation. - -use std::convert::TryFrom; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::spanned::Spanned; -use synthez::{ParseAttrs, Required, Spanning, ToTokens}; - -pub fn derive(input: TokenStream) -> syn::Result { - let input = syn::parse2::(input)?; - let definition = Definition::try_from(input)?; - - Ok(quote! { #definition }) -} - -/// Helper attributes of `#[derive(adapter::Transformer)]` macro. -#[derive(Debug, Default, ParseAttrs)] -pub struct Attrs { - #[parse(nested)] - pub transformer: Required>, -} - -#[derive(Debug, Default, ParseAttrs)] -pub struct TransformerAttrs { - /// Type to derive [`adapter::Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - #[parse(value)] - pub adapter: Required, - - /// [`adapter::Transformer::Transformed`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - #[parse(value, alias = into)] - pub transformed: Required, - - /// [`adapter::Transformer::Context`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Context - #[parse(value, alias = ctx)] - pub context: Required, - - /// [`adapter::Transformer::Error`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Error - #[parse(value, alias = err)] - pub error: Required, -} - -#[derive(Debug, ToTokens)] -#[to_tokens(append(derive_transformer, from_unknown))] -pub struct Definition { - pub ident: syn::Ident, - pub generics: syn::Generics, - pub variants: Vec, - pub adapter: syn::TypePath, - pub transformed: syn::TypePath, - pub context: syn::Type, - pub error: syn::TypePath, -} - -impl TryFrom for Definition { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> syn::Result { - let attrs = Attrs::parse_attrs("event", &input)?; - let TransformerAttrs { - adapter, - transformed, - context, - error, - } = attrs.transformer.into_inner().into_inner(); - - let data = if let syn::Data::Enum(data) = input.data { - data - } else { - return Err(syn::Error::new(input.span(), "expected enum only")); - }; - - Ok(Self { - ident: input.ident, - generics: input.generics, - variants: data.variants.into_iter().collect(), - adapter: adapter.into_inner(), - transformed: transformed.into_inner(), - context: context.into_inner(), - error: error.into_inner(), - }) - } -} - -impl Definition { - fn derive_transformer(&self) -> TokenStream { - let event = &self.ident; - let adapter = &self.adapter; - let context = &self.context; - let error = &self.error; - let transformed = &self.transformed; - let inner_match = self.inner_match(); - let transformed_stream = self.transformed_stream(); - - quote! { - impl ::arcana::es::adapter::Transformer<#event> for #adapter { - type Context = #context; - type Error = #error; - type Transformed = #transformed; - type TransformedStream<'me, 'ctx> = #transformed_stream; - - fn transform<'me, 'ctx>( - &'me self, - event: #event, - context: &'ctx >::Context, - ) -> >:: - TransformedStream<'me, 'ctx> - { - use ::arcana::codegen::futures::StreamExt as _; - - fn transform_result( - res: Result, - ) -> Result - where - IntoOk: From, - IntoErr: From, - { - ::std::result::Result::map_err( - ::std::result::Result::map( - res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - } - - match event { - #inner_match - } - } - } - } - } - - fn transformed_stream(&self) -> TokenStream { - let adapter = &self.adapter; - let from = &self.ident; - - let stream = |ev: TokenStream| quote! { - ::arcana::codegen::futures::stream::Map< - <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: - TransformedStream<'me, 'ctx>, - fn( - Result< - <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: - Transformed, - <#adapter as ::arcana::es::adapter::Transformer<#ev>>:: - Error, - >, - ) -> Result< - <#adapter as ::arcana::es::adapter::Transformer<#from>>:: - Transformed, - <#adapter as ::arcana::es::adapter::Transformer<#from>>:: - Error, - >, - > - }; - - let last_variant= &self - .variants - .last() - .unwrap() - .fields - .iter() - .next() - .unwrap() - .ty; - let last_variant = stream(last_variant.into_token_stream()); - - self - .variants - .iter() - .map(|var| &var.fields.iter().next().unwrap().ty) - .rev() - .skip(1) - .fold(last_variant, |ty, variant| { - let variant = stream(variant.into_token_stream()); - quote! { - ::arcana::codegen::futures::future::Either< - #variant, - #ty, - > - } - }) - } - - fn inner_match(&self) -> TokenStream { - let event = &self.ident; - let adapter = &self.adapter; - - let variant = &self.variants.first().unwrap().ident; - let variant_val = &self - .variants - .first() - .unwrap() - .fields - .iter() - .next() - .unwrap() - .ty; - - let matcher = |variant: TokenStream, variant_val: TokenStream, ext: TokenStream| { - quote! { - #event::#variant(event) => { - <#adapter as ::arcana::es::adapter::Transformer< - #variant_val - >>::transform(self, event, context) - .map(transform_result as fn(_) -> _) - #ext - }, - } - }; - - if self.variants.len() == 1 { - return matcher(variant.into_token_stream(), variant_val.into_token_stream(), quote! {}); - } - - self.variants - .iter() - .enumerate() - .map(|(i, var)| { - let variant = &var.ident; - let variant_val = &var.fields.iter().next().unwrap().ty; - - let left_stream = - (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); - let convert = std::iter::repeat(quote! { .left_stream() }) - .take(left_stream) - .chain( - std::iter::repeat(quote! { .right_stream() }).take(i), - ) - .collect(); - matcher(variant.into_token_stream(), variant_val.into_token_stream(), convert) - }) - .collect() - } - - fn from_unknown(&self) -> TokenStream { - let transformed = &self.transformed; - quote! { - impl From<::arcana::es::adapter::transformer::strategy::Unknown> - for #transformed - { - fn from( - u: ::arcana::es::adapter::transformer::strategy::Unknown, - ) -> Self { - match u {} - } - } - } - } -} diff --git a/codegen/core/src/es/event/mod.rs b/codegen/core/src/es/event/mod.rs index 1788a31..bd7d5e8 100644 --- a/codegen/core/src/es/event/mod.rs +++ b/codegen/core/src/es/event/mod.rs @@ -1,5 +1,6 @@ //! `#[derive(Event)]` macro implementation. +pub mod transformer; pub mod versioned; use std::convert::TryFrom; diff --git a/codegen/core/src/es/event/transformer.rs b/codegen/core/src/es/event/transformer.rs new file mode 100644 index 0000000..1b4e68a --- /dev/null +++ b/codegen/core/src/es/event/transformer.rs @@ -0,0 +1,380 @@ +//! `#[derive(adapter::Transformer)]` macro implementation. + +use std::{convert::TryFrom, iter}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use synthez::{ParseAttrs, Required, Spanning, ToTokens}; + +/// Expands `#[derive(adapter::Transformer)]` macro. +/// +/// # Errors +/// +/// - If `input` isn't a Rust enum definition; +/// - If some enum variant is not a single-field tuple struct; +/// - If failed to parse [`Attrs`]. +pub fn derive(input: TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + let definition = Definition::try_from(input)?; + + Ok(quote! { #definition }) +} + +/// Helper attributes of `#[derive(adapter::Transformer)]` macro. +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { + /// [`InnerAttrs`] for generating [`Transformer`][0] trait impls. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[parse(nested)] + pub transformer: Vec>, +} + +/// Inner attributes of `#[event(transformer(...)]`. +/// +/// Each of them used to generate [`Transformer`][0] trait impl. +/// +/// [0]: arcana_core::es::adapter::Transformer +#[derive(Debug, Default, ParseAttrs)] +pub struct InnerAttrs { + /// Type to derive [`Transformer`][0] on. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[parse(value)] + pub adapter: Required, + + /// [`Transformer::Transformed`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Transformed + #[parse(value, alias = into)] + pub transformed: Required, + + /// [`Transformer::Context`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Context + #[parse(value, alias = ctx)] + pub context: Required, + + /// [`Transformer::Error`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Error + #[parse(value, alias = err)] + pub error: Required, +} + +// TODO: add PartialEq impls in synthez +impl PartialEq for InnerAttrs { + fn eq(&self, other: &Self) -> bool { + *self.adapter == *other.adapter + && *self.transformed == *other.transformed + && *self.context == *other.context + && *self.error == *other.error + } +} + +/// Representation of a enum for implementing [`Transformer`][0], used for code +/// generation. +/// +/// [0]: arcana_core::es::adapter::Transformer +#[derive(Debug, ToTokens)] +#[to_tokens(append(derive_transformer))] +pub struct Definition { + /// Generic parameter of the [`Transformer`][0]. + /// + /// [0]: arcana_core::es::adapter::Transformer + pub event: syn::Ident, + + /// [`syn::Generics`] of this enum's type. + pub generics: syn::Generics, + + /// [`struct@syn::Ident`] and single [`syn::Type`] of every + /// [`syn::FieldsUnnamed`] [`syn::Variant`]. + pub variants: Vec<(syn::Ident, syn::Type)>, + + /// Definitions of structures to derive [`Transformer`][0] on. + /// + /// [0]: arcana_core::es::adapter::Transformer + pub transformers: Vec, +} + +/// Representation of a struct implementing [`Transformer`][0], used for code +/// generation. +/// +/// [0]: arcana_core::es::adapter::Transformer +#[derive(Debug)] +pub struct ImplDefinition { + /// Type to derive [`Transformer`][0] on. + /// + /// [0]: arcana_core::es::adapter::Transformer + pub adapter: syn::Type, + + /// [`Transformer::Transformed`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Transformed + pub transformed: syn::Type, + + /// [`Transformer::Context`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Context + pub context: syn::Type, + + /// [`Transformer::Error`][0] type. + /// + /// [0]: arcana_core::es::adapter::Transformer::Error + pub error: syn::Type, +} + +impl TryFrom for Definition { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> syn::Result { + let attrs: Attrs = Attrs::parse_attrs("event", &input)?; + + if attrs.transformer.is_empty() { + return Err(syn::Error::new( + input.span(), + "expected at least 1 `#[event(transformer(...))` attribute", + )); + } + + let data = if let syn::Data::Enum(data) = input.data { + data + } else { + return Err(syn::Error::new(input.span(), "expected enum only")); + }; + + let variants = data + .variants + .into_iter() + .map(Self::parse_variant) + .collect::>>()?; + + let transformers = attrs + .transformer + .into_iter() + .map(|tr| { + let InnerAttrs { + adapter, + transformed, + context, + error, + } = tr.into_inner(); + ImplDefinition { + adapter: adapter.into_inner(), + transformed: transformed.into_inner(), + context: context.into_inner(), + error: error.into_inner(), + } + }) + .collect(); + + Ok(Self { + event: input.ident, + generics: input.generics, + variants, + transformers, + }) + } +} + +impl Definition { + /// Parses [`syn::Variant`], returning its [`syn::Ident`] and single inner + /// [`syn::Field`]. + /// + /// # Errors + /// + /// If [`syn::Variant`] doesn't have exactly one unnamed 1 [`syn::Field`]. + fn parse_variant( + variant: syn::Variant, + ) -> syn::Result<(syn::Ident, syn::Type)> { + if variant.fields.len() != 1 { + return Err(syn::Error::new( + variant.span(), + "enum variants must have exactly 1 field", + )); + } + if !matches!(variant.fields, syn::Fields::Unnamed(_)) { + return Err(syn::Error::new( + variant.span(), + "only tuple struct enum variants allowed", + )); + } + + Ok((variant.ident, variant.fields.into_iter().next().unwrap().ty)) + } + + /// Generates code to derive [`Transformer`][0] trait. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[must_use] + pub fn derive_transformer(&self) -> TokenStream { + let event = &self.event; + + self.transformers.iter().map(|tr| { + let ImplDefinition { + adapter, + transformed, + context, + error, + } = tr; + let inner_match = self.inner_match(adapter); + let transformed_stream = self.transformed_stream(adapter); + let (impl_gen, type_gen, where_clause) = + self.generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gen ::arcana::es::adapter::Transformer< + #event#type_gen + > for #adapter #where_clause + { + type Context = #context; + type Error = #error; + type Transformed = #transformed; + type TransformedStream<'me, 'ctx> = #transformed_stream; + + fn transform<'me, 'ctx>( + &'me self, + __event: #event, + __context: + &'ctx >::Context, + ) -> >:: + TransformedStream<'me, 'ctx> + { + match __event { + #inner_match + } + } + } + } + }) + .collect() + } + + /// Generates code of [`Transformer::Transformed`][0] associated type. + /// + /// This type is basically a recursive type + /// [`Either`]`>`, where every `VarN` is an + /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a + /// [`stream::Map`] with a function that uses [`From`] impl to transform + /// [`Event`]s into compatible ones. + /// + /// [0]: arcana_core::es::adapter::Transformer::Transformed + /// [1]: arcana_core::es::adapter::Transformer::TransformedStream + /// [`Either`]: futures::future::Either + /// [`Event`]: trait@::arcana_core::es::Event + /// [`stream::Map`]: futures::stream::Map + #[must_use] + pub fn transformed_stream(&self, adapter: &syn::Type) -> TokenStream { + let from = &self.event; + + let transformed_stream = |event: TokenStream| { + quote! { + ::arcana::codegen::futures::stream::Map< + <#adapter as ::arcana::es::adapter::Transformer<#event >>:: + TransformedStream<'me, 'ctx>, + fn( + ::std::result::Result< + <#adapter as ::arcana::es::adapter:: + Transformer<#event >>::Transformed, + <#adapter as ::arcana::es::adapter:: + Transformer<#event >>::Error, + >, + ) -> ::std::result::Result< + <#adapter as ::arcana::es::adapter:: + Transformer<#from>>::Transformed, + <#adapter as ::arcana::es::adapter:: + Transformer<#from>>::Error, + >, + > + } + }; + + self.variants + .iter() + .rev() + .fold(None, |acc, (_, var_ty)| { + let variant_stream = + transformed_stream(var_ty.into_token_stream()); + Some( + acc.map(|acc| { + quote! { + ::arcana::codegen::futures::future::Either< + #variant_stream, + #acc, + > + } + }) + .unwrap_or(variant_stream), + ) + }) + .unwrap_or_default() + } + + /// Generates code for implementation of a [`Transformer::transform()`][0] + /// fn. + /// + /// Generated code matches over every [`Event`]'s variant and makes it + /// compatible with [`Self::transformed_stream()`] type with + /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] + /// combinators. + /// + /// [0]: arcana_core::es::adapter::Transformer::transform + /// [`Event`]: trait@arcana_core::es::Event + /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() + /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() + #[must_use] + pub fn inner_match(&self, adapter: &syn::Type) -> TokenStream { + let event = &self.event; + + self.variants + .iter() + .enumerate() + .map(|(i, (variant_ident, variant_ty))| { + let stream_map = quote! { + ::arcana::codegen::futures::StreamExt::map( + <#adapter as ::arcana::es::adapter::Transformer< + #variant_ty + >>::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + }; + + let right_stream = quote! { + ::arcana::codegen::futures::StreamExt::right_stream + }; + let left_stream = quote! { + ::arcana::codegen::futures::StreamExt::left_stream + }; + let left_stream_count = + (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); + + let transformed_stream = iter::repeat(left_stream) + .take(left_stream_count) + .chain(iter::repeat(right_stream).take(i)) + .fold(stream_map, |acc, stream| { + quote! { #stream(#acc) } + }); + + quote! { + #event::#variant_ident(__event) => { + #transformed_stream + }, + } + }) + .collect() + } +} diff --git a/codegen/core/src/es/mod.rs b/codegen/core/src/es/mod.rs index 3ef94f1..0234461 100644 --- a/codegen/core/src/es/mod.rs +++ b/codegen/core/src/es/mod.rs @@ -3,4 +3,3 @@ //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub mod event; -pub mod adapter; diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 209da50..a651e9e 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -48,7 +48,7 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// [`event::Versioned`]: arcana_core::es::event::Versioned #[proc_macro_derive(EventTransformer, attributes(event))] pub fn derive_event_transformer(input: TokenStream) -> TokenStream { - codegen::es::adapter::derive(input.into()) + codegen::es::event::transformer::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/es/adapter.rs b/codegen/src/es/adapter.rs index dd65fc3..079fa48 100644 --- a/codegen/src/es/adapter.rs +++ b/codegen/src/es/adapter.rs @@ -1 +1,5 @@ +//! [`Transformer`] derive macro. +//! +//! [`Transformer`]: arcana_core::es::adapter::Transformer + pub use arcana_codegen_shim::EventTransformer; diff --git a/examples/event_adapter.rs b/examples/event_adapter.rs index bce3c13..00b9e6e 100644 --- a/examples/event_adapter.rs +++ b/examples/event_adapter.rs @@ -8,7 +8,7 @@ use arcana::es::{ }; use derive_more::From; use either::Either; -use futures::{stream, TryStreamExt as _}; +use futures::{future, stream, TryStreamExt as _}; #[tokio::main] async fn main() { @@ -42,6 +42,28 @@ async fn main() { .unwrap(); println!("context: {}\nevents:{:#?}", ctx, collect); + + let events = stream::once(future::ready( + InputConfirmationSend::ConfirmationSent(EmailConfirmationSent), + )); + let collect = Adapter + .transform_all(events, &ctx) + .try_collect::>() + .await + .unwrap(); + + println!("context: {}\nevents:{:#?}", ctx, collect); + + let events = stream::once(future::ready( + InputConfirmationSend::ConfirmationSent(EmailConfirmationSent), + )); + let collect = SecondAdapter + .transform_all(events, &ctx) + .try_collect::>() + .await + .unwrap(); + + println!("context: {}\nevents:{:#?}", ctx, collect); } // Events definitions @@ -65,14 +87,14 @@ struct EmailConfirmed { confirmed_by: String, } -#[derive(Debug, From, EventTransformer)] +#[derive(Debug, EventTransformer, From)] #[event( transformer( adapter = Adapter, into = EmailAddedOrConfirmed, context = dyn Any, - err = Infallible, - ) + error = Infallible, + ), )] enum InputEmailEvents { ConfirmationSent(EmailConfirmationSent), @@ -81,20 +103,51 @@ enum InputEmailEvents { Confirmed(EmailConfirmed), } +#[derive(Debug, EventTransformer, From)] +#[event( + transformer( + adapter = Adapter, + into = EmailAddedOrConfirmed, + context = dyn Any, + err = Infallible, + ), + transformer( + adapter = SecondAdapter, + into = EmailAddedOrConfirmed, + context = dyn Any, + err = Infallible, + ), +)] +enum InputConfirmationSend { + ConfirmationSent(EmailConfirmationSent), +} + #[derive(Debug, From)] enum EmailAddedOrConfirmed { Added(EmailAdded), Confirmed(EmailConfirmed), } +impl From for EmailAddedOrConfirmed { + fn from(u: strategy::Unknown) -> Self { + match u {} + } +} + // Adapter implementations struct Adapter; +struct SecondAdapter; + impl transformer::WithStrategy for Adapter { type Strategy = strategy::Skip; } +impl transformer::WithStrategy for SecondAdapter { + type Strategy = strategy::Skip; +} + impl transformer::WithStrategy for Adapter { type Strategy = strategy::AsIs; } From 193fd2d5a9e236247ccd5fee90480593be66744a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 09:36:44 +0300 Subject: [PATCH 042/104] Extend support for event::Initial --- codegen/Cargo.toml | 6 +- codegen/impl/src/es/event/mod.rs | 133 ++++++++++++++++++++++--------- codegen/src/unique_events.rs | 8 +- core/Cargo.toml | 1 + core/src/es/event.rs | 46 +++++++++++ examples/event.rs | 39 ++++++--- src/codegen.rs | 7 +- src/es/event.rs | 4 + 8 files changed, 192 insertions(+), 52 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 2af7664..71d3281 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -14,15 +14,13 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documentation +doc = ["arcana-codegen-shim/doc"] # only for generating documentation [dependencies] +arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"] } arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } static_assertions = { version = "1.1", default-features = false } -# `doc` feature -arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 594aa80..e539ac2 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -161,14 +161,22 @@ impl Definition { impl #impl_gens ::arcana::es::Event for #ty#ty_gens #where_clause { fn name(&self) -> ::arcana::es::event::Name { match self { - #( Self::#var(f) => ::arcana::es::Event::name(f), )* + #( + Self::#var(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + )* #unreachable_arm } } fn version(&self) -> ::arcana::es::event::Version { match self { - #( Self::#var(f) => ::arcana::es::Event::version(f), )* + #( + Self::#var(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + )* #unreachable_arm } } @@ -206,6 +214,7 @@ impl Definition { // https://github.com/rust-lang/rust/issues/57775 let ty_subst_gens = Self::substitute_generics_trivially(&self.generics); + #[rustfmt::skip] quote! { #[automatically_derived] #[doc(hidden)] @@ -232,7 +241,9 @@ impl Definition { let mut i = 0; #({ - let events = <#var_ty>::__arcana_events(); + let events = <<#var_ty as + ::arcana::es::event::UnpackInitial>::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -245,10 +256,12 @@ impl Definition { } } - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - #ty::#ty_subst_gens::__arcana_events() - ) + ::arcana::codegen:: + every_combination_of_event_name_and_version_must_correspond_to_single_type!( + !::arcana::codegen::unique_events:: + has_different_types_with_same_name_and_ver( + #ty::#ty_subst_gens::__arcana_events() + ) ); } } @@ -268,20 +281,29 @@ mod spec { } }; + #[rustfmt::skip] let output = quote! { #[automatically_derived] impl ::arcana::es::Event for Event { fn name(&self) -> ::arcana::es::event::Name { match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), + Self::File(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), } } fn version(&self) -> ::arcana::es::event::Version { match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), + Self::File(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), } } } @@ -310,7 +332,9 @@ mod spec { let mut i = 0; { - let events = ::__arcana_events(); + let events = <::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -319,7 +343,9 @@ mod spec { } } { - let events = ::__arcana_events(); + let events = <::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -332,10 +358,12 @@ mod spec { } } - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<>::__arcana_events() - ) + ::arcana::codegen:: + every_combination_of_event_name_and_version_must_correspond_to_single_type!( + !::arcana::codegen::unique_events:: + has_different_types_with_same_name_and_ver( + Event::<>::__arcana_events() + ) ); }; @@ -354,20 +382,29 @@ mod spec { } }; + #[rustfmt::skip] let output = quote! { #[automatically_derived] impl<'a, F, C> ::arcana::es::Event for Event<'a, F, C> { fn name(&self) -> ::arcana::es::event::Name { match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), + Self::File(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), } } fn version(&self) -> ::arcana::es::event::Version { match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), + Self::File(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), } } } @@ -398,7 +435,9 @@ mod spec { let mut i = 0; { - let events = < FileEvent<'a, F> >::__arcana_events(); + let events = << FileEvent<'a, F> as + ::arcana::es::event::UnpackInitial>::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -407,7 +446,9 @@ mod spec { } } { - let events = < ChatEvent<'a, C> >::__arcana_events(); + let events = << ChatEvent<'a, C> as + ::arcana::es::event::UnpackInitial>::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -420,10 +461,12 @@ mod spec { } } - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<'static, (), ()>::__arcana_events() - ) + ::arcana::codegen:: + every_combination_of_event_name_and_version_must_correspond_to_single_type!( + !::arcana::codegen::unique_events:: + has_different_types_with_same_name_and_ver( + Event::<'static, (), ()>::__arcana_events() + ) ); }; @@ -433,6 +476,7 @@ mod spec { ); } + #[allow(clippy::too_many_lines)] #[test] fn ignores_ignored_variant() { let input_ignore = parse_quote! { @@ -452,21 +496,30 @@ mod spec { } }; + #[rustfmt::skip] let output = quote! { #[automatically_derived] impl ::arcana::es::Event for Event { fn name(&self) -> ::arcana::es::event::Name { match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), + Self::File(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::name( + ::arcana::es::event::BorrowInitial::borrow(f) + ), _ => unreachable!(), } } fn version(&self) -> ::arcana::es::event::Version { match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), + Self::File(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), + Self::Chat(f) => ::arcana::es::Event::version( + ::arcana::es::event::BorrowInitial::borrow(f) + ), _ => unreachable!(), } } @@ -496,7 +549,9 @@ mod spec { let mut i = 0; { - let events = ::__arcana_events(); + let events = << FileEvent as + ::arcana::es::event::UnpackInitial>::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -505,7 +560,9 @@ mod spec { } } { - let events = ::__arcana_events(); + let events = << ChatEvent as + ::arcana::es::event::UnpackInitial>::Event + >::__arcana_events(); let mut j = 0; while j < events.len() { res[i] = events[j]; @@ -518,10 +575,12 @@ mod spec { } } - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<>::__arcana_events() - ) + ::arcana::codegen:: + every_combination_of_event_name_and_version_must_correspond_to_single_type!( + !::arcana::codegen::unique_events:: + has_different_types_with_same_name_and_ver( + Event::<>::__arcana_events() + ) ); }; diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index ec55326..8df1c11 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -20,6 +20,8 @@ //! [`event::Version`]: arcana_core::es::event::Version //! [`event::Versioned`]: arcana_core::es::event::Versioned +use arcana_core::es::event; + /// Tracking of number of [`VersionedEvent`]s. /// /// [`VersionedEvent`]: arcana_core::es::VersionedEvent @@ -31,13 +33,17 @@ pub trait UniqueEvents { const COUNT: usize; } +impl UniqueEvents for event::Initial { + const COUNT: usize = Ev::COUNT; +} + /// Checks whether the given array of `events` combinations of [`Event::name`] /// and [`Event::version`] corresponding to different Rust types. /// /// [`Event::name`]: arcana_core::es::Event::name /// [`Event::version`]: arcana_core::es::Event::version #[must_use] -pub const fn has_duplicates( +pub const fn has_different_types_with_same_name_and_ver( events: [(&str, &str, u16); N], ) -> bool { let mut outer = 0; diff --git a/core/Cargo.toml b/core/Cargo.toml index 1c9227b..716fd7d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,6 +19,7 @@ es = [] [dependencies] derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false } ref-cast = "1.0" +sealed = "0.3" [package.metadata.docs.rs] all-features = true diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 58e2cae..d7354e7 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -4,6 +4,7 @@ use std::{convert::TryFrom, num::NonZeroU16}; use derive_more::{Deref, DerefMut, Display, Into}; use ref_cast::RefCast; +use sealed::sealed; /// Fully qualified name of an [`Event`]. pub type Name = &'static str; @@ -110,6 +111,12 @@ pub trait Initialized { #[repr(transparent)] pub struct Initial(pub Ev); +impl From for Initial { + fn from(ev: Ev) -> Self { + Initial(ev) + } +} + impl> Sourced> for Option { @@ -117,3 +124,42 @@ impl> Sourced> *self = Some(S::init(&event.0)); } } + +/// [`Borrow`]-like trait for borrowing [`Event`]s as is or from [`Initial`]. +/// Used in codegen only. +#[sealed] +pub trait BorrowInitial { + /// Borrows [`Event`]. + fn borrow(&self) -> &Borrowed; +} + +#[sealed] +impl BorrowInitial for Initial { + fn borrow(&self) -> &Ev { + &self.0 + } +} + +#[sealed] +impl BorrowInitial for T { + fn borrow(&self) -> &T { + self + } +} + +/// Trait for getting [`Event`] as is or from [`Initial`]. Used in codegen only. +#[sealed] +pub trait UnpackInitial { + /// [`Event`] type. + type Event: ?Sized; +} + +#[sealed] +impl UnpackInitial for Initial { + type Event = Ev; +} + +#[sealed] +impl UnpackInitial for Ev { + type Event = Ev; +} diff --git a/examples/event.rs b/examples/event.rs index b289fdd..3700239 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -1,23 +1,44 @@ use arcana::es::{event, Event}; #[derive(event::Versioned)] -#[event(name = "chat", version = 1)] -struct ChatEvent; +#[event(name = "chat.created", version = 1)] +struct ChatCreated; #[derive(event::Versioned)] -#[event(name = "file", version = 1)] -struct FileEvent; +#[event(name = "message.posted", version = 1)] +struct MessagePosted; + +#[derive(Event)] +enum ChatEvent { + Created(event::Initial), + MessagePosted(MessagePosted), +} + +#[derive(Event)] +enum MessageEvent { + MessagePosted(event::Initial), +} #[derive(Event)] enum AnyEvent { Chat(ChatEvent), - File(FileEvent), + Message(MessageEvent), } fn main() { - let ev = AnyEvent::Chat(ChatEvent); - assert_eq!(ev.name(), "chat"); + let ev = ChatEvent::Created(ChatCreated.into()); + assert_eq!(ev.name(), "chat.created"); + + let ev = ChatEvent::MessagePosted(MessagePosted); + assert_eq!(ev.name(), "message.posted"); + + let ev = MessageEvent::MessagePosted(MessagePosted.into()); + assert_eq!(ev.name(), "message.posted"); + + let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into())); + assert_eq!(ev.name(), "chat.created"); - let ev = AnyEvent::File(FileEvent); - assert_eq!(ev.name(), "file"); + let ev = + AnyEvent::Message(MessageEvent::MessagePosted(MessagePosted.into())); + assert_eq!(ev.name(), "message.posted"); } diff --git a/src/codegen.rs b/src/codegen.rs index 6bcbff7..bd35786 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,7 +1,12 @@ #![doc(hidden)] #[doc(hidden)] +#[rustfmt::skip] pub use arcana_codegen::{ - sa, + // Named so long for better error messages + // TODO: replace with panic once const_panic is stabilized + // https://github.com/rust-lang/rust/issues/51999 + sa::const_assert as + every_combination_of_event_name_and_version_must_correspond_to_single_type, unique_events::{self, UniqueEvents}, }; diff --git a/src/es/event.rs b/src/es/event.rs index ee1af99..ae98726 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -3,7 +3,11 @@ #[cfg(feature = "derive")] #[doc(inline)] pub use arcana_codegen::es::event::{Event, Versioned}; + #[doc(inline)] pub use arcana_core::es::event::{ Event, Initial, Initialized, Name, Sourced, Version, Versioned, }; + +#[doc(hidden)] +pub use arcana_core::es::event::{BorrowInitial, UnpackInitial}; From a8580c8ae5a7f404d9373606b40b432b6d36d6dd Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 09:42:16 +0300 Subject: [PATCH 043/104] Fix docs --- codegen/src/unique_events.rs | 3 ++- core/src/es/event.rs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/codegen/src/unique_events.rs b/codegen/src/unique_events.rs index 8df1c11..ce9e67a 100644 --- a/codegen/src/unique_events.rs +++ b/codegen/src/unique_events.rs @@ -10,7 +10,8 @@ //! method. //! This array consists of unique Rust type identifiers, [`event::Name`]s and //! [`event::Version`]s of all the [`Event`] variants. Correctness is checked -//! then with [`const_assert`]ing the [`has_duplicates()`] function. +//! then with [`const_assert`]ing the +//! [`has_different_types_with_same_name_and_ver()`] function. //! //! [`const_assert`]: static_assertions::const_assert //! [`Event`]: arcana_core::es::Event diff --git a/core/src/es/event.rs b/core/src/es/event.rs index d7354e7..6a249e2 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -127,6 +127,8 @@ impl> Sourced> /// [`Borrow`]-like trait for borrowing [`Event`]s as is or from [`Initial`]. /// Used in codegen only. +/// +/// [`Borrow`]: std::borrow::Borrow #[sealed] pub trait BorrowInitial { /// Borrows [`Event`]. From 197a1cb06f72b8950a76295d3464b2dc05156a20 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 11:42:31 +0300 Subject: [PATCH 044/104] Add docs for Transformer derive macro --- Cargo.toml | 5 +- codegen/Cargo.toml | 1 + codegen/core/Cargo.toml | 31 - codegen/core/README.md | 15 - codegen/core/src/es/event/mod.rs | 619 ------------------ codegen/core/src/es/event/versioned.rs | 299 --------- codegen/core/src/es/mod.rs | 5 - codegen/core/src/lib.rs | 24 - codegen/impl/Cargo.toml | 3 +- codegen/impl/src/es/event/mod.rs | 1 + .../src/es/event/transformer.rs | 41 +- codegen/shim/Cargo.toml | 1 + codegen/shim/src/lib.rs | 134 +++- codegen/src/es/mod.rs | 2 +- codegen/src/es/{adapter.rs => transformer.rs} | 2 +- codegen/src/lib.rs | 2 + core/Cargo.toml | 10 +- core/src/lib.rs | 1 + src/codegen.rs | 5 +- src/es/adapter/mod.rs | 5 +- src/es/adapter/transformer/mod.rs | 10 +- 21 files changed, 182 insertions(+), 1034 deletions(-) delete mode 100644 codegen/core/Cargo.toml delete mode 100644 codegen/core/README.md delete mode 100644 codegen/core/src/es/event/mod.rs delete mode 100644 codegen/core/src/es/event/versioned.rs delete mode 100644 codegen/core/src/es/mod.rs delete mode 100644 codegen/core/src/lib.rs rename codegen/{core => impl}/src/es/event/transformer.rs (94%) rename codegen/src/es/{adapter.rs => transformer.rs} (61%) diff --git a/Cargo.toml b/Cargo.toml index a81a2f8..876b246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,16 +21,13 @@ es = ["arcana-core/es"] [dependencies] arcana-core = { version = "0.1.0-dev", path = "./core" } arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } -futures = "0.3" [dev-dependencies] derive_more = "0.99" either = "1" +futures = "0.3" tokio = { version = "1", features = ["full"] } -[workspace] -members = ["codegen", "codegen/core", "codegen/shim", "core"] - [[example]] name = "event" required-features = ["derive", "es"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 2af7664..21fda1e 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -18,6 +18,7 @@ doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documenta [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } +futures = { version = "0.3", default-features = false } static_assertions = { version = "1.1", default-features = false } # `doc` feature diff --git a/codegen/core/Cargo.toml b/codegen/core/Cargo.toml deleted file mode 100644 index c39c362..0000000 --- a/codegen/core/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "arcana-codegen-core" -version = "0.1.0-dev" -edition = "2018" -resolver = "2" -description = "Core implementations of `arcana-codegen` crate." -authors = [ - "Ilya Solovyiov ", - "Kai Ren " -] -documentation = "https://docs.rs/arcana-codegen-core" -homepage = "https://github.com/arcana-rs/arcana/tree/master/codegen/core" -repository = "https://github.com/arcana-rs/arcana" -readme = "README.md" - -[features] -doc = ["arcana-core", "futures"] # only for generating documentation - -[dependencies] -proc-macro2 = { version = "1.0.4", default-features = false } -quote = { version = "1.0.9", default-features = false } -syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } -synthez = { version = "0.1.3", default-features = false } - -# `doc` feature -arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } -futures = { version = "0.3", default-features = false, optional = true } - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/core/README.md b/codegen/core/README.md deleted file mode 100644 index 4063a19..0000000 --- a/codegen/core/README.md +++ /dev/null @@ -1,15 +0,0 @@ -arcana-codegen-core -=================== - -Core implementations of [`arcana-codegen`] crate. - -DO NOT use it directly, use [`arcana`] crate instead. Unless you need to reuse [`arcana-codegen`] inner machinery in your own [proc macro][1] crate. - - - - - -[`arcana`]: https://docs.rs/arcana -[`arcana-codegen`]: https://docs.rs/arcana-codegen - -[1]: https://doc.rust-lang.org/reference/procedural-macros.html diff --git a/codegen/core/src/es/event/mod.rs b/codegen/core/src/es/event/mod.rs deleted file mode 100644 index bd7d5e8..0000000 --- a/codegen/core/src/es/event/mod.rs +++ /dev/null @@ -1,619 +0,0 @@ -//! `#[derive(Event)]` macro implementation. - -pub mod transformer; -pub mod versioned; - -use std::convert::TryFrom; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::spanned::Spanned; -use synthez::{ParseAttrs, ToTokens}; - -/// Expands `#[derive(Event)]` macro. -/// -/// # Errors -/// -/// - If `input` isn't a Rust enum definition; -/// - If some enum variant is not a single-field tuple struct; -/// - If failed to parse [`VariantAttrs`]. -pub fn derive(input: TokenStream) -> syn::Result { - let input = syn::parse2::(input)?; - let definition = Definition::try_from(input)?; - - Ok(quote! { #definition }) -} - -/// Helper attributes of `#[derive(Event)]` macro placed on an enum variant. -#[derive(Debug, Default, ParseAttrs)] -pub struct VariantAttrs { - /// Indicator whether to ignore this enum variant for code generation. - #[parse(ident, alias = skip)] - pub ignore: Option, -} - -/// Representation of an enum implementing [`Event`], used for code generation. -/// -/// [`Event`]: arcana_core::es::event::Event -#[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event, gen_uniqueness_glue_code))] -pub struct Definition { - /// [`syn::Ident`](struct@syn::Ident) of this enum's type. - pub ident: syn::Ident, - - /// [`syn::Generics`] of this Enum's type. - pub generics: syn::Generics, - - /// Single-[`Field`] [`Variant`]s of this enum to consider in code - /// generation. - /// - /// [`Field`]: syn::Field - /// [`Variant`]: syn::Variant - pub variants: Vec, - - /// Indicator whether this enum has variants marked with `#[event(ignore)]` - /// attribute. - pub has_ignored_variants: bool, -} - -impl TryFrom for Definition { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> syn::Result { - let data = if let syn::Data::Enum(data) = &input.data { - data - } else { - return Err(syn::Error::new( - input.span(), - "expected enum only, \ - consider using `arcana::es::event::Versioned` for structs", - )); - }; - - let variants = data - .variants - .iter() - .filter_map(|v| Self::parse_variant(v).transpose()) - .collect::>>()?; - let has_ignored_variants = variants.len() < data.variants.len(); - - Ok(Self { - ident: input.ident, - generics: input.generics, - variants, - has_ignored_variants, - }) - } -} - -impl Definition { - /// Parses and validates [`syn::Variant`] its [`VariantAttrs`]. - /// - /// # Errors - /// - /// - If [`VariantAttrs`] failed to parse. - /// - If [`syn::Variant`] doesn't have exactly one unnamed 1 [`syn::Field`] - /// and is not ignored. - fn parse_variant( - variant: &syn::Variant, - ) -> syn::Result> { - let attrs = VariantAttrs::parse_attrs("event", variant)?; - if attrs.ignore.is_some() { - return Ok(None); - } - - if variant.fields.len() != 1 { - return Err(syn::Error::new( - variant.span(), - "enum variants must have exactly 1 field", - )); - } - if !matches!(variant.fields, syn::Fields::Unnamed(_)) { - return Err(syn::Error::new( - variant.span(), - "only tuple struct enum variants allowed", - )); - } - - Ok(Some(variant.clone())) - } - - /// Replaces [`syn::Type`] generics with default values. - /// - /// - [`syn::Lifetime`] -> `'static`; - /// - [`syn::Type`] -> `()`; - /// - [`syn::Binding`] -> `Ident = ()`; - /// - `Fn(A, B) -> C` -> `Fn((), ()) -> ()`. - fn replace_type_generics_with_default_values(ty: &syn::Type) -> syn::Type { - match ty { - syn::Type::Path(path) => { - let mut path = path.clone(); - - for segment in &mut path.path.segments { - match &mut segment.arguments { - syn::PathArguments::AngleBracketed(generics) => { - for arg in &mut generics.args { - match arg { - syn::GenericArgument::Lifetime(l) => { - *l = syn::parse_quote!('static); - } - syn::GenericArgument::Type(ty) => { - *ty = syn::parse_quote!(()); - } - syn::GenericArgument::Binding(bind) => { - bind.ty = syn::parse_quote!(()); - } - syn::GenericArgument::Const(_) - | syn::GenericArgument::Constraint(_) => {} - } - } - } - syn::PathArguments::Parenthesized(paren) => { - paren.output = syn::parse_quote!(()); - for input in &mut paren.inputs { - *input = syn::parse_quote!(()); - } - } - syn::PathArguments::None => {} - } - } - - syn::Type::Path(path) - } - ty => ty.clone(), - } - } - - /// Replaces [`syn::Generics`] with default values. - /// - /// - [`syn::Lifetime`] -> `'static`; - /// - [`syn::Type`] -> `()`. - fn replace_generics_with_default_values( - generics: &syn::Generics, - ) -> TokenStream { - let generics = generics.params.iter().map(|param| match param { - syn::GenericParam::Lifetime(_) => quote! { 'static }, - syn::GenericParam::Type(_) => quote! { () }, - syn::GenericParam::Const(c) => quote! { #c }, - }); - - quote! { < #( #generics ),* > } - } - - /// Generates code to derive [`Event`][0] trait, by simply matching over - /// each enum variant, which is expected to be itself an [`Event`][0] - /// implementer. - /// - /// [0]: arcana_core::es::event::Event - #[must_use] - pub fn impl_event(&self) -> TokenStream { - let ty = &self.ident; - let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - - let var = self.variants.iter().map(|v| &v.ident).collect::>(); - - let unreachable_arm = self.has_ignored_variants.then(|| { - quote! { - _ => unreachable!(), - } - }); - - quote! { - #[automatically_derived] - impl #impl_gens ::arcana::es::Event for #ty#ty_gens #where_clause { - fn name(&self) -> ::arcana::es::event::Name { - match self { - #( Self::#var(f) => ::arcana::es::Event::name(f), )* - #unreachable_arm - } - } - - fn version(&self) -> ::arcana::es::event::Version { - match self { - #( Self::#var(f) => ::arcana::es::Event::version(f), )* - #unreachable_arm - } - } - } - } - } - - /// Generates functions, that returns array composed from arrays of all enum - /// variants. - /// - /// Checks uniqueness of all [`Event::name`][0]s and [`Event::version`][1]s. - /// - /// # Panics - /// - /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked - /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. - /// - /// [0]: arcana_core::es::event::Event::name() - /// [1]: arcana_core::es::event::Event::version() - /// [`Field`]: syn::Field - /// [`Variant`]: syn::Variant - #[must_use] - pub fn gen_uniqueness_glue_code(&self) -> TokenStream { - let ty = &self.ident; - let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - - let default_generics = - Self::replace_generics_with_default_values(&self.generics); - - let (var_ty, var_ty_with_default_generics): (Vec<_>, Vec<_>) = self - .variants - .iter() - .flat_map(|v| &v.fields) - .map(|f| { - ( - &f.ty, - Self::replace_type_generics_with_default_values(&f.ty), - ) - }) - .unzip(); - - // TODO: use `Self::__arcana_events()` inside impl, once - // https://github.com/rust-lang/rust/issues/57775 is resolved. - quote! { - #[automatically_derived] - #[doc(hidden)] - impl #impl_gens ::arcana::codegen::UniqueEvents for #ty#ty_gens - #where_clause - { - #[doc(hidden)] - const COUNT: usize = - #( <#var_ty as ::arcana::codegen::UniqueEvents>::COUNT )+*; - } - - #[automatically_derived] - #[doc(hidden)] - impl #ty #default_generics { - #[doc(hidden)] - pub const fn __arcana_events() -> [ - (&'static str, u16); - ::COUNT - ] { - let mut res = [ - ("", 0); - ::COUNT - ]; - - let mut i = 0; - #({ - let events = - <#var_ty_with_default_generics>::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - })* - - res - } - } - - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - #ty::#default_generics::__arcana_events() - ) - ); - } - } -} - -#[cfg(test)] -mod spec { - use super::{derive, quote}; - - #[test] - fn derives_enum_impl() { - let input = syn::parse_quote! { - enum Event { - File(FileEvent), - Chat(ChatEvent), - } - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::es::Event for Event { - fn name(&self) -> ::arcana::es::event::Name { - match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), - } - } - - fn version(&self) -> ::arcana::es::event::Version { - match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), - } - } - } - - #[automatically_derived] - #[doc(hidden)] - impl ::arcana::codegen::UniqueEvents for Event { - #[doc(hidden)] - const COUNT: usize = - ::COUNT + - ::COUNT; - } - - #[automatically_derived] - #[doc(hidden)] - impl Event<> { - #[doc(hidden)] - pub const fn __arcana_events() -> [ - (&'static str, u16); - ::COUNT - ] { - let mut res = [ - ("", 0); - ::COUNT - ]; - - let mut i = 0; - - { - let events = ::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - { - let events = ::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - res - } - } - - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<>::__arcana_events() - ) - ); - }; - - assert_eq!(derive(input).unwrap().to_string(), output.to_string()); - } - - #[test] - fn derives_enum_with_generics_impl() { - let input = syn::parse_quote! { - enum Event<'a, F, C> { - File(FileEvent<'a, F>), - Chat(ChatEvent<'a, C>), - } - }; - - let output = quote! { - #[automatically_derived] - impl<'a, F, C> ::arcana::es::Event for Event<'a, F, C> { - fn name(&self) -> ::arcana::es::event::Name { - match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), - } - } - - fn version(&self) -> ::arcana::es::event::Version { - match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), - } - } - } - - #[automatically_derived] - #[doc(hidden)] - impl<'a, F, C> ::arcana::codegen::UniqueEvents for Event<'a, F, C> { - #[doc(hidden)] - const COUNT: usize = - as ::arcana::codegen::UniqueEvents> - ::COUNT + - as ::arcana::codegen::UniqueEvents> - ::COUNT; - } - - #[automatically_derived] - #[doc(hidden)] - impl Event<'static, (), ()> { - #[doc(hidden)] - pub const fn __arcana_events() -> [ - (&'static str, u16); - ::COUNT - ] { - let mut res = [ - ("", 0); - ::COUNT - ]; - - let mut i = 0; - - { - let events = - < FileEvent<'static, ()> >::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - { - let events = - < ChatEvent<'static, ()> >::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - res - } - } - - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<'static, (), ()>::__arcana_events() - ) - ); - }; - - assert_eq!(derive(input).unwrap().to_string(), output.to_string()); - } - - #[test] - fn skip_unique_check_on_variant() { - let input_skip = syn::parse_quote! { - enum Event { - File(FileEvent), - Chat(ChatEvent), - #[event(skip)] - _NonExhaustive - } - }; - - let input_ignore = syn::parse_quote! { - enum Event { - File(FileEvent), - Chat(ChatEvent), - #[event(ignore)] - _NonExhaustive - } - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::es::Event for Event { - fn name(&self) -> ::arcana::es::event::Name { - match self { - Self::File(f) => ::arcana::es::Event::name(f), - Self::Chat(f) => ::arcana::es::Event::name(f), - _ => unreachable!(), - } - } - - fn version(&self) -> ::arcana::es::event::Version { - match self { - Self::File(f) => ::arcana::es::Event::version(f), - Self::Chat(f) => ::arcana::es::Event::version(f), - _ => unreachable!(), - } - } - } - - #[automatically_derived] - #[doc(hidden)] - impl ::arcana::codegen::UniqueEvents for Event { - #[doc(hidden)] - const COUNT: usize = - ::COUNT + - ::COUNT; - } - - #[automatically_derived] - #[doc(hidden)] - impl Event<> { - #[doc(hidden)] - pub const fn __arcana_events() -> [ - (&'static str, u16); - ::COUNT - ] { - let mut res = [ - ("", 0); - ::COUNT - ]; - - let mut i = 0; - - { - let events = ::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - { - let events = ::__arcana_events(); - let mut j = 0; - while j < events.len() { - res[i] = events[j]; - j += 1; - i += 1; - } - } - - res - } - } - - ::arcana::codegen::sa::const_assert!( - !::arcana::codegen::unique_events::has_duplicates( - Event::<>::__arcana_events() - ) - ); - }; - - let input_skip = derive(input_skip).unwrap().to_string(); - let input_ignore = derive(input_ignore).unwrap().to_string(); - assert_eq!(input_skip, input_ignore); - assert_eq!(input_skip, output.to_string()); - } - - #[test] - fn errors_on_multiple_fields_in_variant() { - let input = syn::parse_quote! { - enum Event { - Event1(Event1), - Event2 { - event: Event2, - second_field: Event3, - } - } - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "enum variants must have exactly 1 field", - ); - } - - #[test] - fn errors_on_struct() { - let input = syn::parse_quote! { - struct Event; - }; - - let error = derive(input).unwrap_err(); - - assert_eq!( - format!("{}", error), - "expected enum only, \ - consider using `arcana::es::event::Versioned` for structs", - ); - } -} diff --git a/codegen/core/src/es/event/versioned.rs b/codegen/core/src/es/event/versioned.rs deleted file mode 100644 index c632621..0000000 --- a/codegen/core/src/es/event/versioned.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! `#[derive(event::Versioned)]` macro implementation. - -use std::{convert::TryFrom, num::NonZeroU16}; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::spanned::Spanned as _; -use synthez::{ParseAttrs, Required, ToTokens}; - -/// Expands `#[derive(event::Versioned)]` macro. -/// -/// # Errors -/// -/// - If `input` isn't a Rust struct definition; -/// - If failed to parse [`Attrs`]. -pub fn derive(input: TokenStream) -> syn::Result { - let input = syn::parse2::(input)?; - let definition = Definition::try_from(input)?; - - Ok(quote! { #definition }) -} - -/// Helper attributes of `#[derive(event::Versioned)]` macro. -#[derive(Debug, Default, ParseAttrs)] -pub struct Attrs { - /// Value to return by [`event::Versioned::name()`][0] method. - /// - /// [0]: arcana_core::es::event::Versioned::name - #[parse(value)] - pub name: Required, - - /// Value to return by [`event::Versioned::version()`][0] method. - /// - /// [0]: arcana_core::es::event::Versioned::version - #[parse(value, alias = ver, validate = parses_as_non_zero_u16)] - pub version: Required, -} - -/// If `val` is [`Some`], checks if it can be parsed to [`NonZeroU16`]. -fn parses_as_non_zero_u16(val: &Required) -> syn::Result<()> { - syn::LitInt::base10_parse::(&**val).map(drop) -} - -/// Representation of a struct implementing [`event::Versioned`][0], used for -/// code generation. -/// -/// [0]: arcana_core::es::event::Versioned -#[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event_versioned, gen_uniqueness_glue_code))] -pub struct Definition { - /// [`syn::Ident`](struct@syn::Ident) of this structure's type. - pub ident: syn::Ident, - - /// [`syn::Generics`] of this structure's type. - pub generics: syn::Generics, - - /// Value to return by [`event::Versioned::name()`][0] method in the - /// generated code. - /// - /// [0]: arcana_core::es::event::Versioned::name - pub event_name: syn::LitStr, - - /// Value to return by [`event::Versioned::version()`][0] method in the - /// generated code. - /// - /// [0]: arcana_core::es::event::Versioned::version - pub event_version: syn::LitInt, -} - -impl TryFrom for Definition { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> syn::Result { - if !matches!(input.data, syn::Data::Struct(..)) { - return Err(syn::Error::new( - input.span(), - "expected struct only, \ - consider using `arcana::es::Event` for enums", - )); - } - - let attrs = Attrs::parse_attrs("event", &input)?; - - Ok(Self { - ident: input.ident, - generics: input.generics, - // TODO: Use `.into_inner()` once available. - event_name: (*attrs.name).clone(), - event_version: (*attrs.version).clone(), - }) - } -} - -impl Definition { - /// Generates code to derive [`event::Versioned`][0] trait. - /// - /// [0]: arcana_core::es::event::Versioned - #[must_use] - pub fn impl_event_versioned(&self) -> TokenStream { - let ty = &self.ident; - let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - - let (event_name, event_ver) = (&self.event_name, &self.event_version); - - quote! { - #[automatically_derived] - impl #impl_gens ::arcana::es::event::Versioned for #ty#ty_gens - #where_clause - { - fn name() -> ::arcana::es::event::Name { - #event_name - } - - fn version() -> ::arcana::es::event::Version { - // SAFETY: Safe, as checked by proc macro in compile time. - unsafe { - ::arcana::es::event::Version::new_unchecked(#event_ver) - } - } - } - } - } - - /// Generates hidden machinery code used to check uniqueness of - /// [`Event::name`] and [`Event::version`]. - /// - /// [`Event::name`]: arcana_core::es::Event::name - /// [`Event::version`]: arcana_core::es::Event::version - #[must_use] - pub fn gen_uniqueness_glue_code(&self) -> TokenStream { - let ty = &self.ident; - let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - - let (event_name, event_ver) = (&self.event_name, &self.event_version); - - quote! { - #[automatically_derived] - #[doc(hidden)] - impl #impl_gens ::arcana::codegen::UniqueEvents for #ty#ty_gens - #where_clause - { - #[doc(hidden)] - const COUNT: usize = 1; - } - - #[automatically_derived] - #[doc(hidden)] - impl #impl_gens #ty#ty_gens #where_clause { - #[doc(hidden)] - #[inline] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [(#event_name, #event_ver)] - } - } - } - } -} - -#[cfg(test)] -mod spec { - use quote::quote; - use syn::parse_quote; - - #[test] - fn derives_struct_impl() { - let input = parse_quote! { - #[event(name = "event", version = 1)] - struct Event; - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::es::event::Versioned for Event { - fn name() -> ::arcana::es::event::Name { - "event" - } - - fn version() -> ::arcana::es::event::Version { - // SAFETY: Safe, as checked by proc macro in compile time. - unsafe { ::arcana::es::event::Version::new_unchecked(1) } - } - } - - #[automatically_derived] - #[doc(hidden)] - impl ::arcana::codegen::UniqueEvents for Event { - #[doc(hidden)] - const COUNT: usize = 1; - } - - #[automatically_derived] - #[doc(hidden)] - impl Event { - #[doc(hidden)] - #[inline] - pub const fn __arcana_events() -> [(&'static str, u16); 1] { - [("event", 1)] - } - } - }; - - assert_eq!( - super::derive(input).unwrap().to_string(), - output.to_string(), - ); - } - - #[test] - fn name_arg_is_required() { - let input = parse_quote! { - #[event(ver = 1)] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - format!("{}", err), - "`name` argument of `#[event]` attribute is expected to be \ - present, but is absent", - ); - } - - #[test] - fn version_arg_is_required() { - let input = parse_quote! { - #[event(name = "event")] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - format!("{}", err), - "either `ver` or `version` argument of `#[event]` attribute is \ - expected to be present, but is absent", - ); - } - - #[test] - fn errors_on_negative_version() { - let input = parse_quote! { - #[event(name = "event", ver = -1)] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!(format!("{}", err), "invalid digit found in string"); - } - - #[test] - fn errors_on_zero_version() { - let input = parse_quote! { - #[event(name = "event", version = 0)] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - format!("{}", err), - "number would be zero for non-zero type", - ); - } - - #[test] - fn errors_on_u16_overflowed_version() { - let input = parse_quote! { - #[event(name = "event", version = 4294967295)] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - format!("{}", err), - "number too large to fit in target type", - ); - } - - #[test] - fn errors_on_enum() { - let input = parse_quote! { - #[event(name = "event", version = 1)] - enum Event { - Event1(Event1), - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - format!("{}", err), - "expected struct only, \ - consider using `arcana::es::Event` for enums", - ); - } -} diff --git a/codegen/core/src/es/mod.rs b/codegen/core/src/es/mod.rs deleted file mode 100644 index 0234461..0000000 --- a/codegen/core/src/es/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Code generation related to [Event Sourcing]. -//! -//! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html - -pub mod event; diff --git a/codegen/core/src/lib.rs b/codegen/core/src/lib.rs deleted file mode 100644 index 7a0aaf8..0000000 --- a/codegen/core/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![doc = include_str!("../README.md")] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![deny( - nonstandard_style, - rust_2018_idioms, - rustdoc::broken_intra_doc_links, - rustdoc::private_intra_doc_links, - trivial_casts, - trivial_numeric_casts -)] -#![forbid(non_ascii_idents, unsafe_code)] -#![warn( - deprecated_in_future, - missing_copy_implementations, - missing_debug_implementations, - missing_docs, - unreachable_pub, - unused_import_braces, - unused_labels, - unused_qualifications, - unused_results -)] - -pub mod es; diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index bb36f62..574fcc0 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -doc = ["arcana-core"] # only for generating documentation +doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] proc-macro2 = { version = "1.0.4", default-features = false } @@ -24,6 +24,7 @@ synthez = { version = "0.1.3", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], optional = true } +futures = { version = "0.3", default-features = false, optional = true } [package.metadata.docs.rs] all-features = true diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index f807461..6aa8ba3 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -1,5 +1,6 @@ //! `#[derive(Event)]` macro implementation. +pub mod transformer; pub mod versioned; use std::convert::TryFrom; diff --git a/codegen/core/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs similarity index 94% rename from codegen/core/src/es/event/transformer.rs rename to codegen/impl/src/es/event/transformer.rs index 1b4e68a..7a5a819 100644 --- a/codegen/core/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -24,7 +24,7 @@ pub fn derive(input: TokenStream) -> syn::Result { /// Helper attributes of `#[derive(adapter::Transformer)]` macro. #[derive(Debug, Default, ParseAttrs)] pub struct Attrs { - /// [`InnerAttrs`] for generating [`Transformer`][0] trait impls. + /// [`Vec`] of [`InnerAttrs`] for generating [`Transformer`][0] trait impls. /// /// [0]: arcana_core::es::adapter::Transformer #[parse(nested)] @@ -63,6 +63,23 @@ pub struct InnerAttrs { pub error: Required, } +impl From for ImplDefinition { + fn from(attrs: InnerAttrs) -> Self { + let InnerAttrs { + adapter, + transformed, + context, + error, + } = attrs; + Self { + adapter: adapter.into_inner(), + transformed: transformed.into_inner(), + context: context.into_inner(), + error: error.into_inner(), + } + } +} + // TODO: add PartialEq impls in synthez impl PartialEq for InnerAttrs { fn eq(&self, other: &Self) -> bool { @@ -153,20 +170,7 @@ impl TryFrom for Definition { let transformers = attrs .transformer .into_iter() - .map(|tr| { - let InnerAttrs { - adapter, - transformed, - context, - error, - } = tr.into_inner(); - ImplDefinition { - adapter: adapter.into_inner(), - transformed: transformed.into_inner(), - context: context.into_inner(), - error: error.into_inner(), - } - }) + .map(|tr| tr.into_inner().into()) .collect(); Ok(Self { @@ -255,7 +259,7 @@ impl Definition { /// Generates code of [`Transformer::Transformed`][0] associated type. /// - /// This type is basically a recursive type + /// This is basically a recursive type /// [`Either`]`>`, where every `VarN` is an /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a /// [`stream::Map`] with a function that uses [`From`] impl to transform @@ -270,7 +274,7 @@ impl Definition { pub fn transformed_stream(&self, adapter: &syn::Type) -> TokenStream { let from = &self.event; - let transformed_stream = |event: TokenStream| { + let transformed_stream = |event: &syn::Type| { quote! { ::arcana::codegen::futures::stream::Map< <#adapter as ::arcana::es::adapter::Transformer<#event >>:: @@ -296,8 +300,7 @@ impl Definition { .iter() .rev() .fold(None, |acc, (_, var_ty)| { - let variant_stream = - transformed_stream(var_ty.into_token_stream()); + let variant_stream = transformed_stream(var_ty); Some( acc.map(|acc| { quote! { diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 7f7fcfe..d996948 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -28,6 +28,7 @@ arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], o [dev-dependencies] arcana = { path = "../..", features = ["derive", "es"] } +derive_more = { version = "0.99", features = ["from"], default-features = false } [package.metadata.docs.rs] all-features = true diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 7d5feb9..797cbec 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -151,7 +151,139 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .into() } -/// TODO +/// Macro for deriving [`Transformer`] on [`Adapter`]s to transform derived +/// [`Event`] enum. +/// +/// # Struct attributes +/// +/// #### `#[event(transformer(adapter = ))]` +/// +/// [`Adapter`] to derive [`Transformer`] on. +/// +/// Provided [`Adapter`] must be able to [`Transformer::transform`][0] every +/// enum's variant. +/// +/// #### `#[event(transformer(transformed = ))]` +/// +/// [`Transformer::Transformed`][1] type for [`Transformer`] impl. +/// +/// #### `#[event(transformer(context = ))]` +/// +/// [`Transformer::Context`][2] type for [`Transformer`] impl. +/// +/// #### `#[event(transformer(error = ))]` +/// +/// [`Transformer::Error`][3] type for [`Transformer`] impl. +/// +/// # Example +/// +/// ```rust +/// # #![feature(generic_associated_types)] +/// # +/// # use std::{any::Any, convert::Infallible}; +/// # +/// # use arcana::es::adapter::transformer::{self, strategy, Transformer}; +/// # use derive_more::From; +/// # +/// struct Adapter; +/// +/// struct InputEvent; +/// +/// impl transformer::WithStrategy for Adapter { +/// type Strategy = strategy::Into; +/// } +/// +/// impl From for OutputEvent { +/// fn from(_: InputEvent) -> Self { +/// OutputEvent +/// } +/// } +/// +/// struct OutputEvent; +/// +/// #[derive(From, Transformer)] +/// #[event( +/// transformer( +/// adapter = Adapter, +/// transformed = OutputEvents, +/// context = dyn Any, +/// error = Infallible, +/// ) +/// )] +/// enum InputEvents { +/// Input(InputEvent), +/// } +/// +/// #[derive(From)] +/// enum OutputEvents { +/// Output(OutputEvent), +/// } +/// ``` +/// +/// > __NOTE__: Single [`Event`] enum can be [`Transformer::transform`][0]ed by +/// > multiple [`Adapter`]s. +/// +/// ```rust +/// # #![feature(generic_associated_types)] +/// # +/// # use std::{any::Any, convert::Infallible}; +/// # +/// # use arcana::es::adapter::transformer::{self, strategy, Transformer}; +/// # use derive_more::From; +/// # +/// # struct FirstAdapter; +/// # +/// # struct SecondAdapter; +/// # +/// # struct InputEvent; +/// # +/// # impl transformer::WithStrategy for FirstAdapter { +/// # type Strategy = strategy::Into; +/// # } +/// # +/// # impl transformer::WithStrategy for SecondAdapter { +/// # type Strategy = strategy::Into; +/// # } +/// # +/// # impl From for OutputEvent { +/// # fn from(_: InputEvent) -> Self { +/// # OutputEvent +/// # } +/// # } +/// # +/// # struct OutputEvent; +/// # +/// #[derive(From, Transformer)] +/// #[event( +/// transformer( +/// adapter = FirstAdapter, +/// transformed = OutputEvents, +/// context = dyn Any, +/// error = Infallible, +/// ), +/// transformer( +/// adapter = SecondAdapter, +/// transformed = OutputEvents, +/// context = dyn Any, +/// error = Infallible, +/// ), +/// )] +/// enum InputEvents { +/// Input(InputEvent), +/// } +/// # +/// # #[derive(From)] +/// # enum OutputEvents { +/// # Output(OutputEvent), +/// # } +/// ``` +/// [0]: arcana_core::es::adapter::Transformer::transform() +/// [1]: arcana_core::es::adapter::Transformer::Transformed +/// [2]: arcana_core::es::adapter::Transformer::Context +/// [3]: arcana_core::es::adapter::Transformer::Error +/// [`Adapter`]: arcana_core::es::Adapter +/// [`Event`]: trait@arcana_core::es::Event +/// [`Transformer`]: arcana_core::es::adapter::Transformer #[proc_macro_derive(EventTransformer, attributes(event))] pub fn derive_event_transformer(input: TokenStream) -> TokenStream { codegen::es::event::transformer::derive(input.into()) diff --git a/codegen/src/es/mod.rs b/codegen/src/es/mod.rs index aa1965a..dedc845 100644 --- a/codegen/src/es/mod.rs +++ b/codegen/src/es/mod.rs @@ -2,5 +2,5 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html -pub mod adapter; pub mod event; +pub mod transformer; diff --git a/codegen/src/es/adapter.rs b/codegen/src/es/transformer.rs similarity index 61% rename from codegen/src/es/adapter.rs rename to codegen/src/es/transformer.rs index 079fa48..d69b6af 100644 --- a/codegen/src/es/adapter.rs +++ b/codegen/src/es/transformer.rs @@ -2,4 +2,4 @@ //! //! [`Transformer`]: arcana_core::es::adapter::Transformer -pub use arcana_codegen_shim::EventTransformer; +pub use arcana_codegen_shim::EventTransformer as Transformer; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 57f85aa..0a2ba75 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -26,4 +26,6 @@ pub mod es; pub mod unique_events; +pub use futures; + pub use static_assertions as sa; diff --git a/core/Cargo.toml b/core/Cargo.toml index 61e1de4..3d20ba6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,13 +14,13 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -es = [] +es = ["derive_more", "futures", "pin-project", "ref-cast"] [dependencies] -derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false } -futures = "0.3" -pin-project = "1.0" -ref-cast = "1.0" +derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false, optional = true } +futures = { version = "0.3", default-features = false, optional = true } +pin-project = { version = "1.0", optional = true } +ref-cast = { version = "1.0", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/core/src/lib.rs b/core/src/lib.rs index cd17820..6f36a58 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_cfg))] +#![feature(generic_associated_types)] #![deny( nonstandard_style, rust_2018_idioms, diff --git a/src/codegen.rs b/src/codegen.rs index 528dd26..d60f6f2 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -2,9 +2,6 @@ #[doc(hidden)] pub use arcana_codegen::{ - sa, + futures, sa, unique_events::{self, UniqueEvents}, }; - -#[doc(hidden)] -pub use futures; diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 7a265c4..f6659a8 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -3,6 +3,7 @@ pub mod transformer; #[doc(inline)] -pub use arcana_core::es::adapter::{Adapter, TransformedStream, Transformer}; +pub use self::transformer::Transformer; -pub use arcana_codegen::es::adapter::EventTransformer as Transformer; +#[doc(inline)] +pub use arcana_core::es::adapter::{Adapter, TransformedStream}; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index e1c2900..3b53c4e 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -3,6 +3,10 @@ pub mod strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{ - Strategy, Transformer, WithStrategy, -}; +pub use self::strategy::Strategy; + +#[doc(inline)] +pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; + +#[doc(inline)] +pub use arcana_codegen::es::transformer::Transformer; From 6b95a104bbbb178fcbb649606af4ef77cf9d8881 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 12:34:10 +0300 Subject: [PATCH 045/104] Cover Transformer derive macro with unit tests --- codegen/impl/src/es/event/mod.rs | 2 +- codegen/impl/src/es/event/transformer.rs | 316 ++++++++++++++++++++++- 2 files changed, 316 insertions(+), 2 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 6aa8ba3..a2fdc95 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -547,7 +547,7 @@ mod spec { let err = super::derive(input).unwrap_err(); - assert_eq!(err.to_string(), "enum variants must have exactly 1 field",); + assert_eq!(err.to_string(), "enum variants must have exactly 1 field"); } #[test] diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index 7a5a819..141d542 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -146,6 +146,7 @@ impl TryFrom for Definition { type Error = syn::Error; fn try_from(input: syn::DeriveInput) -> syn::Result { + let span = input.span(); let attrs: Attrs = Attrs::parse_attrs("event", &input)?; if attrs.transformer.is_empty() { @@ -166,6 +167,12 @@ impl TryFrom for Definition { .into_iter() .map(Self::parse_variant) .collect::>>()?; + if variants.is_empty() { + return Err(syn::Error::new( + span, + "enum must have at least one variant", + )); + } let transformers = attrs .transformer @@ -340,7 +347,7 @@ impl Definition { ::arcana::codegen::futures::StreamExt::map( <#adapter as ::arcana::es::adapter::Transformer< #variant_ty - >>::transform(self, __event, __context), + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { ::std::result::Result::map_err( @@ -381,3 +388,310 @@ impl Definition { .collect() } } + +#[cfg(test)] +mod spec { + use quote::quote; + use syn::parse_quote; + + #[allow(clippy::too_many_lines)] + #[test] + fn derives_enum_impl() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + into = IntoEvent, + context = dyn Any, + error = Infallible, + ), + )] + enum Event { + File(FileEvent), + Chat(ChatEvent), + } + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::es::adapter::Transformer for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = IntoEvent; + type TransformedStream<'me, 'ctx> = + ::arcana::codegen::futures::future::Either< + ::arcana::codegen::futures::stream::Map< + >::TransformedStream<'me, 'ctx>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + >, + >, + ::arcana::codegen::futures::stream::Map< + >::TransformedStream<'me, 'ctx>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + >, + >, + >; + + fn transform<'me, 'ctx>( + &'me self, + __event: Event, + __context: + &'ctx >::Context, + ) -> >:: + TransformedStream<'me, 'ctx> + { + match __event { + Event::File(__event) => { + ::arcana::codegen::futures::StreamExt::left_stream( + ::arcana::codegen::futures::StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + Event::Chat(__event) => { + ::arcana::codegen::futures::StreamExt::right_stream( + ::arcana::codegen::futures::StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + } + } + } + }; + + assert_eq!( + super::derive(input).unwrap().to_string(), + output.to_string(), + ); + } + + #[test] + fn errors_on_without_adapter_attribute() { + let input = parse_quote! { + #[event( + transformer( + transformed = IntoEvent, + context = dyn Any, + error = Infallible, + ), + )] + enum Event { + Event1(Event1), + Event2(Event2), + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + err.to_string(), + "`adapter` argument of `#[event(transformer)]` attribute is \ + expected to be present, but is absent", + ); + } + + #[test] + fn errors_on_without_transformed_attribute() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + context = dyn Any, + error = Infallible, + ), + )] + enum Event { + Event1(Event1), + Event2(Event2), + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + err.to_string(), + "either `into` or `transformed` argument of \ + `#[event(transformer)]` attribute is expected to be present, \ + but is absent", + ); + } + + #[test] + fn errors_on_without_context_attribute() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + transformed = IntoEvent, + error = Infallible, + ), + )] + enum Event { + Event1(Event1), + Event2(Event2), + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + err.to_string(), + "either `context` or `ctx` argument of \ + `#[event(transformer)]` attribute is expected to be present, \ + but is absent", + ); + } + + #[test] + fn errors_on_without_error_attribute() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + transformed = IntoEvent, + ctx = dyn Any, + ), + )] + enum Event { + Event1(Event1), + Event2(Event2), + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + err.to_string(), + "either `err` or `error` argument of \ + `#[event(transformer)]` attribute is expected to be present, \ + but is absent", + ); + } + + #[test] + fn errors_on_multiple_fields_in_variant() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + into = IntoEvent, + context = dyn Any, + error = Infallible, + ), + )] + enum Event { + Event1(Event1), + Event2 { + event: Event2, + second_field: Event3, + } + } + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!(err.to_string(), "enum variants must have exactly 1 field"); + } + + #[test] + fn errors_on_struct() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + into = IntoEvent, + context = dyn Any, + error = Infallible, + ), + )] + struct Event; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!(err.to_string(), "expected enum only"); + } + + #[test] + fn errors_on_empty_enum() { + let input = parse_quote! { + #[event( + transformer( + adapter = Adapter, + into = IntoEvent, + context = dyn Any, + error = Infallible, + ), + )] + enum Event {} + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!(err.to_string(), "enum must have at least one variant"); + } +} From c03777ad3aaf793c1423fd6b3d34e4ff24f70e52 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 14:30:33 +0300 Subject: [PATCH 046/104] Add chat example --- .github/workflows/ci.yml | 2 + Cargo.toml | 10 +- core/src/es/adapter/transformer/strategy.rs | 41 +++- examples/chat/Cargo.toml | 17 ++ examples/chat/src/event/chat.rs | 25 +++ examples/chat/src/event/message.rs | 5 + examples/chat/src/event/mod.rs | 37 ++++ examples/chat/src/main.rs | 30 +++ examples/chat/src/storage/chat.rs | 30 +++ examples/chat/src/storage/mod.rs | 50 +++++ examples/event.rs | 44 ----- examples/event_adapter.rs | 201 -------------------- src/es/adapter/transformer/strategy.rs | 2 +- 13 files changed, 235 insertions(+), 259 deletions(-) create mode 100644 examples/chat/Cargo.toml create mode 100644 examples/chat/src/event/chat.rs create mode 100644 examples/chat/src/event/message.rs create mode 100644 examples/chat/src/event/mod.rs create mode 100644 examples/chat/src/main.rs create mode 100644 examples/chat/src/storage/chat.rs create mode 100644 examples/chat/src/storage/mod.rs delete mode 100644 examples/event.rs delete mode 100644 examples/event_adapter.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f5968b..10ca460 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,7 @@ jobs: - arcana-codegen-shim - arcana-codegen - arcana + - arcana-chat-example os: - ubuntu - macOS @@ -134,6 +135,7 @@ jobs: - arcana-codegen-shim - arcana-codegen - arcana + - arcana-chat-example runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 876b246..f80f103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,17 +28,9 @@ either = "1" futures = "0.3" tokio = { version = "1", features = ["full"] } -[[example]] -name = "event" -required-features = ["derive", "es"] - -[[example]] -name = "event_adapter" -required-features = ["derive", "es"] - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [workspace] -members = ["codegen", "codegen/impl", "codegen/shim", "core"] +members = ["codegen", "codegen/impl", "codegen/shim", "core", "examples/chat"] diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index 9a0c95e..a7040aa 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -8,7 +8,9 @@ use std::{ marker::PhantomData, }; -use futures::{future, stream, Stream, StreamExt as _}; +use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; + +use crate::es::event; use super::{Transformer, WithStrategy}; @@ -71,7 +73,38 @@ where } } -/// Strategy for skipping [`Event`]s. +/// [`Strategy`] for wrapping [`Event`]s in [`Initial`]. +/// +/// [`Event`]: crate::es::Event +/// [`Initial`]: event::Initial +#[derive(Clone, Debug)] +pub struct Initialized(PhantomData); + +impl Strategy + for Initialized +where + InnerStrategy: Strategy, +{ + type Context = InnerStrategy::Context; + type Error = InnerStrategy::Error; + type Transformed = event::Initial; + type TransformedStream<'me, 'ctx> = stream::MapOk< + InnerStrategy::TransformedStream<'me, 'ctx>, + WrapInitial, + >; + + fn transform<'me, 'ctx>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) + } +} + +type WrapInitial = fn(Event) -> event::Initial; + +/// [`Strategy`] for skipping [`Event`]s. /// /// Until [never] is stabilized, [`Adapter::Transformed`] must implement /// [`From`] [`Unknown`]. @@ -106,7 +139,7 @@ impl Strategy for Skip { } } -/// Just passes [`Event`] as is, without any conversions. +/// [`Strategy`] for passing [`Event`]s as is, without any conversions. /// /// [`Event`]: crate::es::Event #[derive(Clone, Copy, Debug)] @@ -128,7 +161,7 @@ impl Strategy for AsIs { } } -/// Converts [`Event`] using [`From`] impl. +/// [`Strategy`] for converting [`Event`]s using [`From`] impl. /// /// [`Event`]: crate::es::Event pub struct Into(PhantomData); diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml new file mode 100644 index 0000000..98174f7 --- /dev/null +++ b/examples/chat/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "arcana-chat-example" +version = "0.1.0-dev" +edition = "2018" +resolver = "2" +description = "Simple chat app using arcana framework." +authors = [ + "Ilya Solovyiov ", + "Kai Ren ", +] +publish = false + +[dependencies] +arcana = { version = "0.1.0-dev", path = "../..", features = ["derive", "es"] } +derive_more = "0.99" +futures = "0.3" +tokio = { version = "1", features = ["full"] } diff --git a/examples/chat/src/event/chat.rs b/examples/chat/src/event/chat.rs new file mode 100644 index 0000000..cc6cc94 --- /dev/null +++ b/examples/chat/src/event/chat.rs @@ -0,0 +1,25 @@ +use arcana::es::event; + +pub mod private { + use super::event; + + #[derive(Debug, event::Versioned)] + #[event(name = "chat.private.created", version = 2)] + pub struct Created; +} + +pub mod public { + use super::event; + + #[derive(Debug, event::Versioned)] + #[event(name = "chat.public.created", version = 2)] + pub struct Created; +} + +pub mod v1 { + use super::event; + + #[derive(Debug, event::Versioned)] + #[event(name = "chat.created", version = 1)] + pub struct Created; +} diff --git a/examples/chat/src/event/message.rs b/examples/chat/src/event/message.rs new file mode 100644 index 0000000..fa34a7d --- /dev/null +++ b/examples/chat/src/event/message.rs @@ -0,0 +1,5 @@ +use arcana::es::event; + +#[derive(Debug, event::Versioned)] +#[event(name = "message.posted", version = 1)] +pub struct Posted; diff --git a/examples/chat/src/event/mod.rs b/examples/chat/src/event/mod.rs new file mode 100644 index 0000000..98d667f --- /dev/null +++ b/examples/chat/src/event/mod.rs @@ -0,0 +1,37 @@ +pub mod chat; +pub mod message; + +use arcana::es::{event, Event}; +use derive_more::From; + +#[derive(Debug, Event, From)] +pub enum Chat { + PrivateCreated(event::Initial), + PublicCreated(event::Initial), + MessagePosted(message::Posted), +} + +#[derive(Debug, Event, From)] +pub enum Message { + MessagePosted(event::Initial), +} + +#[cfg(test)] +mod spec { + use super::{chat, message, Chat, Event as _, Message}; + + #[test] + fn event_names() { + let ev = Chat::PrivateCreated(chat::private::Created.into()); + assert_eq!(ev.name(), "chat.private.created"); + + let ev = Chat::PublicCreated(chat::public::Created.into()); + assert_eq!(ev.name(), "chat.public.created"); + + let ev = Chat::MessagePosted(message::Posted); + assert_eq!(ev.name(), "message.posted"); + + let ev = Message::MessagePosted(message::Posted.into()); + assert_eq!(ev.name(), "message.posted"); + } +} diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs new file mode 100644 index 0000000..a047078 --- /dev/null +++ b/examples/chat/src/main.rs @@ -0,0 +1,30 @@ +#![feature(generic_associated_types)] + +mod event; +mod storage; + +use std::array; + +use arcana::es::adapter::Adapter as _; +use futures::{stream, Stream, TryStreamExt as _}; + +#[allow(clippy::semicolon_if_nothing_returned)] +#[tokio::main] +async fn main() { + let events = storage::chat::Adapter + .transform_all(incoming_events(), &()) + .try_collect::>() + .await + .unwrap(); + println!("{:?}", events); +} + +fn incoming_events() -> impl Stream { + stream::iter(array::IntoIter::new([ + storage::ChatEvents::Created(event::chat::v1::Created).into(), + storage::ChatEvents::PrivateCreated(event::chat::private::Created) + .into(), + storage::ChatEvents::PublicCreated(event::chat::public::Created).into(), + storage::MessageEvents::Posted(event::message::Posted).into(), + ])) +} diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs new file mode 100644 index 0000000..0feb281 --- /dev/null +++ b/examples/chat/src/storage/chat.rs @@ -0,0 +1,30 @@ +use arcana::es::adapter::transformer::{self, strategy}; + +use crate::event; + +#[derive(Debug)] +pub struct Adapter; + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::AsIs; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = + strategy::Initialized>; +} + +// Chats are private by default. +impl From for event::chat::private::Created { + fn from(_: event::chat::v1::Created) -> Self { + Self + } +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs new file mode 100644 index 0000000..de70d4e --- /dev/null +++ b/examples/chat/src/storage/mod.rs @@ -0,0 +1,50 @@ +pub mod chat; + +use std::{any::Any, convert::Infallible}; + +use arcana::es::adapter::Transformer; +use derive_more::From; + +use crate::event; + +#[derive(Debug, From, Transformer)] +#[event( + transformer( + adapter = chat::Adapter, + into = event::Chat, + ctx = dyn Any, + err = Infallible, + ), +)] +pub enum Events { + Chat(ChatEvents), + Message(MessageEvents), +} + +#[derive(Debug, From, Transformer)] +#[event( + transformer( + adapter = chat::Adapter, + into = event::Chat, + ctx = dyn Any, + err = Infallible, + ), +)] +pub enum ChatEvents { + Created(event::chat::v1::Created), + PublicCreated(event::chat::public::Created), + PrivateCreated(event::chat::private::Created), +} + +#[derive(Debug, From, Transformer)] +#[event( + transformer( + adapter = chat::Adapter, + into = event::Chat, + ctx = dyn Any, + err = Infallible, + ), +)] +pub enum MessageEvents { + Posted(event::message::Posted), +} diff --git a/examples/event.rs b/examples/event.rs deleted file mode 100644 index 3700239..0000000 --- a/examples/event.rs +++ /dev/null @@ -1,44 +0,0 @@ -use arcana::es::{event, Event}; - -#[derive(event::Versioned)] -#[event(name = "chat.created", version = 1)] -struct ChatCreated; - -#[derive(event::Versioned)] -#[event(name = "message.posted", version = 1)] -struct MessagePosted; - -#[derive(Event)] -enum ChatEvent { - Created(event::Initial), - MessagePosted(MessagePosted), -} - -#[derive(Event)] -enum MessageEvent { - MessagePosted(event::Initial), -} - -#[derive(Event)] -enum AnyEvent { - Chat(ChatEvent), - Message(MessageEvent), -} - -fn main() { - let ev = ChatEvent::Created(ChatCreated.into()); - assert_eq!(ev.name(), "chat.created"); - - let ev = ChatEvent::MessagePosted(MessagePosted); - assert_eq!(ev.name(), "message.posted"); - - let ev = MessageEvent::MessagePosted(MessagePosted.into()); - assert_eq!(ev.name(), "message.posted"); - - let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into())); - assert_eq!(ev.name(), "chat.created"); - - let ev = - AnyEvent::Message(MessageEvent::MessagePosted(MessagePosted.into())); - assert_eq!(ev.name(), "message.posted"); -} diff --git a/examples/event_adapter.rs b/examples/event_adapter.rs deleted file mode 100644 index 00b9e6e..0000000 --- a/examples/event_adapter.rs +++ /dev/null @@ -1,201 +0,0 @@ -#![feature(generic_associated_types)] - -use std::{any::Any, array, convert::Infallible}; - -use arcana::es::{ - adapter::transformer::{self, strategy}, - EventAdapter as _, EventTransformer, -}; -use derive_more::From; -use either::Either; -use futures::{future, stream, TryStreamExt as _}; - -#[tokio::main] -async fn main() { - let ctx = 1_usize; // Can be any type in this example. - let events = stream::iter::<[InputEmailEvents; 5]>([ - EmailConfirmed { - confirmed_by: "1".to_string(), - } - .into(), - EmailAdded { - email: "2".to_string(), - } - .into(), - EmailAddedAndConfirmed { - email: "3".to_string(), - confirmed_by: Some("3".to_string()), - } - .into(), - EmailAddedAndConfirmed { - email: "4".to_string(), - confirmed_by: None, - } - .into(), - EmailConfirmationSent.into(), - ]); - - let collect = Adapter - .transform_all(events, &ctx) - .try_collect::>() - .await - .unwrap(); - - println!("context: {}\nevents:{:#?}", ctx, collect); - - let events = stream::once(future::ready( - InputConfirmationSend::ConfirmationSent(EmailConfirmationSent), - )); - let collect = Adapter - .transform_all(events, &ctx) - .try_collect::>() - .await - .unwrap(); - - println!("context: {}\nevents:{:#?}", ctx, collect); - - let events = stream::once(future::ready( - InputConfirmationSend::ConfirmationSent(EmailConfirmationSent), - )); - let collect = SecondAdapter - .transform_all(events, &ctx) - .try_collect::>() - .await - .unwrap(); - - println!("context: {}\nevents:{:#?}", ctx, collect); -} - -// Events definitions - -#[derive(Debug)] -struct EmailConfirmationSent; - -#[derive(Debug)] -struct EmailAddedAndConfirmed { - email: String, - confirmed_by: Option, -} - -#[derive(Debug)] -struct EmailAdded { - email: String, -} - -#[derive(Debug)] -struct EmailConfirmed { - confirmed_by: String, -} - -#[derive(Debug, EventTransformer, From)] -#[event( - transformer( - adapter = Adapter, - into = EmailAddedOrConfirmed, - context = dyn Any, - error = Infallible, - ), -)] -enum InputEmailEvents { - ConfirmationSent(EmailConfirmationSent), - AddedAndConfirmed(EmailAddedAndConfirmed), - Added(EmailAdded), - Confirmed(EmailConfirmed), -} - -#[derive(Debug, EventTransformer, From)] -#[event( - transformer( - adapter = Adapter, - into = EmailAddedOrConfirmed, - context = dyn Any, - err = Infallible, - ), - transformer( - adapter = SecondAdapter, - into = EmailAddedOrConfirmed, - context = dyn Any, - err = Infallible, - ), -)] -enum InputConfirmationSend { - ConfirmationSent(EmailConfirmationSent), -} - -#[derive(Debug, From)] -enum EmailAddedOrConfirmed { - Added(EmailAdded), - Confirmed(EmailConfirmed), -} - -impl From for EmailAddedOrConfirmed { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -// Adapter implementations - -struct Adapter; - -struct SecondAdapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for SecondAdapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::AsIs; -} - -impl transformer::WithStrategy for Adapter { - // In this case can also be strategy::AsIs. - type Strategy = strategy::Into; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Split>; -} - -impl - strategy::Splitter< - EmailAddedAndConfirmed, - Either, - > for Adapter -{ - type Iterator = SplitEmail; - - fn split(&self, event: EmailAddedAndConfirmed) -> Self::Iterator { - use either::{Left, Right}; - - #[allow(clippy::option_if_let_else)] // use of moved value - if let Some(confirmed_by) = event.confirmed_by { - Right(array::IntoIter::new([ - Left(EmailAdded { email: event.email }), - Right(EmailConfirmed { confirmed_by }), - ])) - } else { - Left(array::IntoIter::new([Left(EmailAdded { - email: event.email, - })])) - } - } -} - -type SplitEmail = Either< - array::IntoIter, 1>, - array::IntoIter, 2>, ->; - -impl From> for EmailAddedOrConfirmed { - fn from(val: Either) -> Self { - match val { - Either::Left(added) => EmailAddedOrConfirmed::Added(added), - Either::Right(conf) => EmailAddedOrConfirmed::Confirmed(conf), - } - } -} diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index d8bf17b..01537de 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,5 +2,5 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Into, Skip, Split, Splitter, Strategy, Unknown, + AsIs, Initialized, Into, Skip, Split, Splitter, Strategy, Unknown, }; From 1ae2c55c105e98edcf5e3479af7f9ff9fc024c6e Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 14:33:04 +0300 Subject: [PATCH 047/104] Add chat example --- examples/chat/src/main.rs | 10 +++++----- examples/chat/src/storage/mod.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index a047078..e15f52a 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -19,12 +19,12 @@ async fn main() { println!("{:?}", events); } -fn incoming_events() -> impl Stream { +fn incoming_events() -> impl Stream { stream::iter(array::IntoIter::new([ - storage::ChatEvents::Created(event::chat::v1::Created).into(), - storage::ChatEvents::PrivateCreated(event::chat::private::Created) + storage::ChatEvent::Created(event::chat::v1::Created).into(), + storage::ChatEvent::PrivateCreated(event::chat::private::Created) .into(), - storage::ChatEvents::PublicCreated(event::chat::public::Created).into(), - storage::MessageEvents::Posted(event::message::Posted).into(), + storage::ChatEvent::PublicCreated(event::chat::public::Created).into(), + storage::MessageEvent::Posted(event::message::Posted).into(), ])) } diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index de70d4e..0aea786 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -16,9 +16,9 @@ use crate::event; err = Infallible, ), )] -pub enum Events { - Chat(ChatEvents), - Message(MessageEvents), +pub enum Event { + Chat(ChatEvent), + Message(MessageEvent), } #[derive(Debug, From, Transformer)] @@ -30,7 +30,7 @@ pub enum Events { err = Infallible, ), )] -pub enum ChatEvents { +pub enum ChatEvent { Created(event::chat::v1::Created), PublicCreated(event::chat::public::Created), PrivateCreated(event::chat::private::Created), @@ -45,6 +45,6 @@ pub enum ChatEvents { err = Infallible, ), )] -pub enum MessageEvents { +pub enum MessageEvent { Posted(event::message::Posted), } From 3acdb0891e3e34ff2da414b2cf47f912883313f0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 2 Sep 2021 15:05:42 +0300 Subject: [PATCH 048/104] Bootstrap [skip ci] --- codegen/impl/src/es/event/mod.rs | 50 +++++++++++++++++++++++++++++++- core/src/es/event.rs | 12 ++++++-- examples/event.rs | 9 ++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 3f50fc4..f15fc47 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned as _; +use syn::{spanned::Spanned as _, parse_quote}; use synthez::{ParseAttrs, ToTokens}; /// Expands `#[derive(Event)]` macro. @@ -184,6 +184,54 @@ impl Definition { } } + /// Generates code to derive [`event::Sourced`][0] trait, by simply matching + /// each enum variant, which is expected to have itself + /// [`event::Sourced`][0] implementation. + /// + /// [0]: arcana_core::es::event::Sourced + #[must_use] + pub fn impl_event_sourced(&self) -> TokenStream { + let ty = &self.ident; + let (_, ty_gens, _) = self.generics.split_for_impl(); + + let var_ty = self + .variants + .iter() + .flat_map(|v| &v.fields) + .map(|f| &f.ty); + + let mut ext_gens = self.generics.clone(); + ext_gens.params.push(parse_quote! { __S }); + ext_gens.make_where_clause().predicates.push(parse_quote! { + Option<__S>: #( ::arcana::es::event::Sourced<#var_ty> )+* + }); + let (impl_gens, _, where_clause) = ext_gens.split_for_impl(); + + let var = self.variants.iter().map(|v| &v.ident); + + let unreachable_arm = self.has_ignored_variants.then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::arcana::es::event::Sourced<#ty#ty_gens> + for Option<__S> #where_clause + { + fn apply(&mut self, event: &#ty#ty_gens) { + match event { + #( + Self::#var(f) => :arcana::es::event::Sourced::apply( + self, f, + ), + )* + #unreachable_arm + } + } + } + } + } + /// Generates hidden machinery code used to statically check that all the /// [`Event::name`][0]s and [`Event::version`][1]s pairs are corresponding /// to a single Rust type. diff --git a/core/src/es/event.rs b/core/src/es/event.rs index e9314e6..a09db14 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -90,7 +90,7 @@ pub trait Sourced { fn apply(&mut self, event: &Ev); } -impl> Sourced for Option { +impl> Sourced for Option { fn apply(&mut self, event: &Ev) { if let Some(state) = self { state.apply(event); @@ -98,9 +98,18 @@ impl> Sourced for Option { } } +impl<'e, S: Sourced> Sourced for Option { + fn apply(&mut self, event: &(dyn Event + 'e)) { + if let Some(state) = self { + state.apply(event); + } + } +} + /// Before a state can be [`Sourced`] it needs to be [`Initialized`]. pub trait Initialized { /// Creates an initial state from the given [`Event`]. + #[must_use] fn init(event: &Ev) -> Self; } @@ -247,7 +256,6 @@ pub mod codegen { /// /// [`Eq`]: std::cmp::Eq // TODO: Remove once `Eq` trait is allowed in `const` context. - #[must_use] const fn str_eq(l: &str, r: &str) -> bool { if l.len() != r.len() { return false; diff --git a/examples/event.rs b/examples/event.rs index b9b3e0f..5125f0d 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -14,6 +14,15 @@ enum ChatEvent { MessagePosted(MessagePosted), } +impl event::Sourced for Option +where Self: event::Sourced> + + event::Sourced +{ + fn apply(&mut self, event: &ChatEvent) { + unimplemented!() + } +} + #[derive(Event)] enum MessageEvent { MessagePosted(Initial), From 81f713a0d88922c49bb906df3ca284f9ac5e2f9c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 15:07:06 +0300 Subject: [PATCH 049/104] WIP [skip ci] --- codegen/impl/src/es/event/transformer.rs | 31 +++++++++++++++--------- core/src/es/adapter/mod.rs | 9 +++++++ examples/chat/src/main.rs | 2 +- src/es/adapter/mod.rs | 4 +++ src/es/adapter/transformer/mod.rs | 1 + 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index 141d542..abde309 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -283,7 +283,7 @@ impl Definition { let transformed_stream = |event: &syn::Type| { quote! { - ::arcana::codegen::futures::stream::Map< + ::arcana::es::adapter::codegen::futures::stream::Map< <#adapter as ::arcana::es::adapter::Transformer<#event >>:: TransformedStream<'me, 'ctx>, fn( @@ -311,7 +311,8 @@ impl Definition { Some( acc.map(|acc| { quote! { - ::arcana::codegen::futures::future::Either< + ::arcana::es::adapter::codegen::futures::future:: + Either< #variant_stream, #acc, > @@ -344,7 +345,7 @@ impl Definition { .enumerate() .map(|(i, (variant_ident, variant_ty))| { let stream_map = quote! { - ::arcana::codegen::futures::StreamExt::map( + ::arcana::es::adapter::codegen::futures::StreamExt::map( <#adapter as ::arcana::es::adapter::Transformer< #variant_ty > >::transform(self, __event, __context), @@ -364,10 +365,12 @@ impl Definition { }; let right_stream = quote! { - ::arcana::codegen::futures::StreamExt::right_stream + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream }; let left_stream = quote! { - ::arcana::codegen::futures::StreamExt::left_stream + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream }; let left_stream_count = (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); @@ -419,8 +422,8 @@ mod spec { type Error = Infallible; type Transformed = IntoEvent; type TransformedStream<'me, 'ctx> = - ::arcana::codegen::futures::future::Either< - ::arcana::codegen::futures::stream::Map< + ::arcana::es::adapter::codegen::futures::future::Either< + ::arcana::es::adapter::codegen::futures::stream::Map< >::TransformedStream<'me, 'ctx>, @@ -441,7 +444,7 @@ mod spec { Transformer>::Error, >, >, - ::arcana::codegen::futures::stream::Map< + ::arcana::es::adapter::codegen::futures::stream::Map< >::TransformedStream<'me, 'ctx>, @@ -475,8 +478,10 @@ mod spec { { match __event { Event::File(__event) => { - ::arcana::codegen::futures::StreamExt::left_stream( - ::arcana::codegen::futures::StreamExt::map( + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( >::transform(self, __event, __context), @@ -497,8 +502,10 @@ mod spec { ) }, Event::Chat(__event) => { - ::arcana::codegen::futures::StreamExt::right_stream( - ::arcana::codegen::futures::StreamExt::map( + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( >::transform(self, __event, __context), diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index ffad6ae..778fba2 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -175,3 +175,12 @@ where } } } + +#[cfg(feature = "codegen")] +pub mod codegen { + //! Re-exports for [`Transformer`] derive macro. + //! + //! [`Transformer`]: crate::es::adapter::Transformer + + pub use futures; +} diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index e15f52a..404097c 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -5,7 +5,7 @@ mod storage; use std::array; -use arcana::es::adapter::Adapter as _; +use arcana::es::EventAdapter as _; use futures::{stream, Stream, TryStreamExt as _}; #[allow(clippy::semicolon_if_nothing_returned)] diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index f6659a8..b59bda7 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,3 +7,7 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{Adapter, TransformedStream}; + +#[cfg(feature = "derive")] +#[doc(inline)] +pub use arcana_core::es::adapter::codegen; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index 3b53c4e..657ffc4 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -8,5 +8,6 @@ pub use self::strategy::Strategy; #[doc(inline)] pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; +#[cfg(feature = "derive")] #[doc(inline)] pub use arcana_codegen::es::transformer::Transformer; From 8f49c41b1f6501668ecc73817b4e623e6bebaf50 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 2 Sep 2021 16:07:45 +0300 Subject: [PATCH 050/104] WIP [skip ci] --- codegen/impl/src/es/event/mod.rs | 81 +++++++++++++++++++++++++++----- examples/event.rs | 23 +++++---- 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index f15fc47..d7151ab 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use proc_macro2::TokenStream; use quote::quote; -use syn::{spanned::Spanned as _, parse_quote}; +use syn::{parse_quote, spanned::Spanned as _}; use synthez::{ParseAttrs, ToTokens}; /// Expands `#[derive(Event)]` macro. @@ -35,7 +35,7 @@ pub struct VariantAttrs { /// /// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event, gen_uniqueness_glue_code))] +#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, @@ -194,19 +194,18 @@ impl Definition { let ty = &self.ident; let (_, ty_gens, _) = self.generics.split_for_impl(); - let var_ty = self - .variants - .iter() - .flat_map(|v| &v.fields) - .map(|f| &f.ty); + let var_ty = + self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); let mut ext_gens = self.generics.clone(); ext_gens.params.push(parse_quote! { __S }); ext_gens.make_where_clause().predicates.push(parse_quote! { - Option<__S>: #( ::arcana::es::event::Sourced<#var_ty> )+* + Self: #( ::arcana::es::event::Sourced<#var_ty> )+* }); let (impl_gens, _, where_clause) = ext_gens.split_for_impl(); + let turbofish_gens = ty_gens.as_turbofish(); + let var = self.variants.iter().map(|v| &v.ident); let unreachable_arm = self.has_ignored_variants.then(|| { @@ -220,11 +219,9 @@ impl Definition { { fn apply(&mut self, event: &#ty#ty_gens) { match event { - #( - Self::#var(f) => :arcana::es::event::Sourced::apply( - self, f, - ), - )* + #(#ty#turbofish_gens::#var(f) => { + ::arcana::es::event::Sourced::apply(self, f) + },)* #unreachable_arm } } @@ -318,6 +315,7 @@ mod spec { use quote::quote; use syn::parse_quote; + #[allow(clippy::too_many_lines)] #[test] fn derives_enum_impl() { let input = parse_quote! { @@ -353,6 +351,24 @@ mod spec { } } + #[automatically_derived] + impl<__S> ::arcana::es::event::Sourced for Option<__S> + where + Self: ::arcana::es::event::Sourced + + ::arcana::es::event::Sourced + { + fn apply(&mut self, event: &Event) { + match event { + Event::File(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + Event::Chat(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { @@ -422,6 +438,7 @@ mod spec { ); } + #[allow(clippy::too_many_lines)] #[test] fn derives_enum_with_generics_impl() { let input = parse_quote! { @@ -457,6 +474,25 @@ mod spec { } } + #[automatically_derived] + impl<'a, F, C, __S> ::arcana::es::event::Sourced > + for Option<__S> + where + Self: ::arcana::es::event::Sourced > + + ::arcana::es::event::Sourced > + { + fn apply(&mut self, event: &Event<'a, F, C>) { + match event { + Event::<'a, F, C>::File(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + Event::<'a, F, C>::Chat(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl<'a, F, C> ::arcana::es::event::codegen::Versioned @@ -576,6 +612,25 @@ mod spec { } } + #[automatically_derived] + impl<__S> ::arcana::es::event::Sourced for Option<__S> + where + Self: ::arcana::es::event::Sourced + + ::arcana::es::event::Sourced + { + fn apply(&mut self, event: &Event) { + match event { + Event::File(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + Event::Chat(f) => { + ::arcana::es::event::Sourced::apply(self, f) + }, + _ => unreachable!(), + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { diff --git a/examples/event.rs b/examples/event.rs index 5125f0d..7c7fe98 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -1,4 +1,4 @@ -use arcana::es::event::{self, Event, Initial}; +use arcana::es::event::{self, Event, Initial, Sourced as _}; #[derive(event::Versioned)] #[event(name = "chat.created", version = 1)] @@ -14,15 +14,6 @@ enum ChatEvent { MessagePosted(MessagePosted), } -impl event::Sourced for Option -where Self: event::Sourced> + - event::Sourced -{ - fn apply(&mut self, event: &ChatEvent) { - unimplemented!() - } -} - #[derive(Event)] enum MessageEvent { MessagePosted(Initial), @@ -34,6 +25,15 @@ enum AnyEvent { Message(MessageEvent), } +#[derive(Debug, Eq, PartialEq)] +struct Message; + +impl event::Initialized for Message { + fn init(_: &MessagePosted) -> Self { + Self + } +} + fn main() { let ev = ChatEvent::Created(ChatCreated.into()); assert_eq!(ev.name(), "chat.created"); @@ -42,6 +42,9 @@ fn main() { assert_eq!(ev.name(), "message.posted"); let ev = MessageEvent::MessagePosted(MessagePosted.into()); + let mut msg: Option = None; + msg.apply(&ev); + assert_eq!(msg, Some(Message)); assert_eq!(ev.name(), "message.posted"); let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into())); From 49c107313e19fb185d22926007d398bf22eeed9d Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 3 Sep 2021 09:13:54 +0300 Subject: [PATCH 051/104] Add event::Sourcing and examples of usage --- codegen/impl/src/es/event/mod.rs | 3 +- core/Cargo.toml | 3 ++ core/src/es/event.rs | 47 ++++++++++++++++++++++++++++++++ core/src/es/mod.rs | 4 +-- examples/event.rs | 37 +++++++++++++++++++++---- src/es/event.rs | 2 +- src/es/mod.rs | 4 +-- 7 files changed, 88 insertions(+), 12 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index d7151ab..e4c149a 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -193,6 +193,7 @@ impl Definition { pub fn impl_event_sourced(&self) -> TokenStream { let ty = &self.ident; let (_, ty_gens, _) = self.generics.split_for_impl(); + let turbofish_gens = ty_gens.as_turbofish(); let var_ty = self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); @@ -204,8 +205,6 @@ impl Definition { }); let (impl_gens, _, where_clause) = ext_gens.split_for_impl(); - let turbofish_gens = ty_gens.as_turbofish(); - let var = self.variants.iter().map(|v| &v.ident); let unreachable_arm = self.has_ignored_variants.then(|| { diff --git a/core/Cargo.toml b/core/Cargo.toml index ecc2af4..d695e35 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,6 +22,9 @@ derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", " ref-cast = "1.0" sealed = { version = "0.3", optional = true } +[dev-dependencies] +arcana = { version = "0.1.0-dev", path = "..", features = ["derive", "es"] } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/core/src/es/event.rs b/core/src/es/event.rs index a09db14..9d43ffd 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -106,6 +106,53 @@ impl<'e, S: Sourced> Sourced for Option { } } +/// [`Event`] that can source state `S`. Shouldn't be implemented manually, +/// rather used as blanket impl. +/// +/// # Example +/// +/// ```rust +/// # use arcana::es::event::{self, Sourced as _}; +/// # +/// #[derive(Debug, Eq, PartialEq)] +/// struct Chat; +/// +/// #[derive(event::Versioned)] +/// #[event(name = "chat", version = 1)] +/// struct ChatEvent; +/// +/// impl event::Initialized for Chat { +/// fn init(_: &ChatEvent) -> Self { +/// Self +/// } +/// } +/// +/// let mut chat = Option::::None; +/// let ev = event::Initial(ChatEvent); +/// let ev: &dyn event::Sourcing> = &ev; +/// chat.apply(ev); +/// assert_eq!(chat, Some(Chat)); +/// ``` +pub trait Sourcing { + /// Applies the specified [`Event`] to the current state. + fn apply_to(&self, state: &mut S); +} + +impl Sourcing for Ev +where + S: Sourced, +{ + fn apply_to(&self, state: &mut S) { + state.apply(self); + } +} + +impl<'e, S> Sourced + 'e> for S { + fn apply(&mut self, event: &(dyn Sourcing + 'e)) { + event.apply_to(self); + } +} + /// Before a state can be [`Sourced`] it needs to be [`Initialized`]. pub trait Initialized { /// Creates an initial state from the given [`Event`]. diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 16464f5..937ce77 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -7,6 +7,6 @@ pub mod event; #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, - Name as EventName, Sourced as EventSourced, Version as EventVersion, - Versioned as VersionedEvent, + Name as EventName, Sourced as EventSourced, Sourcing as EventSourcing, + Version as EventVersion, Versioned as VersionedEvent, }; diff --git a/examples/event.rs b/examples/event.rs index 7c7fe98..4f6b87a 100644 --- a/examples/event.rs +++ b/examples/event.rs @@ -1,4 +1,4 @@ -use arcana::es::event::{self, Event, Initial, Sourced as _}; +use arcana::es::event::{self, Event, Initial, Initialized, Sourced, Sourcing}; #[derive(event::Versioned)] #[event(name = "chat.created", version = 1)] @@ -25,27 +25,54 @@ enum AnyEvent { Message(MessageEvent), } +#[derive(Debug, Eq, PartialEq)] +struct Chat { + message_count: usize, +} + +impl Initialized for Chat { + fn init(_: &ChatCreated) -> Self { + Self { message_count: 0 } + } +} + +impl Sourced for Chat { + fn apply(&mut self, _: &MessagePosted) { + self.message_count += 1; + } +} + #[derive(Debug, Eq, PartialEq)] struct Message; -impl event::Initialized for Message { +impl Initialized for Message { fn init(_: &MessagePosted) -> Self { Self } } fn main() { + let mut chat = Option::::None; + let mut message = Option::::None; + let ev = ChatEvent::Created(ChatCreated.into()); + chat.apply(&ev); assert_eq!(ev.name(), "chat.created"); + assert_eq!(chat, Some(Chat { message_count: 0 })); let ev = ChatEvent::MessagePosted(MessagePosted); + chat.apply(&ev); assert_eq!(ev.name(), "message.posted"); + assert_eq!(chat, Some(Chat { message_count: 1 })); + + let ev: &dyn Sourcing> = &ev; + chat.apply(ev); + assert_eq!(chat, Some(Chat { message_count: 2 })); let ev = MessageEvent::MessagePosted(MessagePosted.into()); - let mut msg: Option = None; - msg.apply(&ev); - assert_eq!(msg, Some(Message)); + message.apply(&ev); assert_eq!(ev.name(), "message.posted"); + assert_eq!(message, Some(Message)); let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into())); assert_eq!(ev.name(), "chat.created"); diff --git a/src/es/event.rs b/src/es/event.rs index 3f94c6f..bac97e7 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -2,7 +2,7 @@ #[doc(inline)] pub use arcana_core::es::event::{ - Event, Initial, Initialized, Name, Sourced, Version, Versioned, + Event, Initial, Initialized, Name, Sourced, Sourcing, Version, Versioned, }; #[cfg(feature = "derive")] diff --git a/src/es/mod.rs b/src/es/mod.rs index 16464f5..937ce77 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -7,6 +7,6 @@ pub mod event; #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, - Name as EventName, Sourced as EventSourced, Version as EventVersion, - Versioned as VersionedEvent, + Name as EventName, Sourced as EventSourced, Sourcing as EventSourcing, + Version as EventVersion, Versioned as VersionedEvent, }; From 2cca4633531cb53b12831e96fd6beecd8078cdd8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 3 Sep 2021 09:26:45 +0300 Subject: [PATCH 052/104] Add docs --- codegen/shim/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 41a7a5d..8fe9fb6 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -35,6 +35,9 @@ use proc_macro::TokenStream; /// is that all the underlying [`Event`] or [`Versioned`] impls should be /// derived too. /// +/// Also provides [`Sourced`] impl for every state, which can be sourced from +/// all variants. +/// /// > __WARNING:__ Currently may not work with complex generics using where /// > clause because of `const` evaluation limitations. Should be /// > lifted once [rust-lang/rust#57775] is resolved. @@ -87,7 +90,7 @@ use proc_macro::TokenStream; /// #[derive(Event)] /// enum AnyEvent { /// Chat(ChatEvent), -/// #[event(ignore)] +/// #[event(ignore)] // Not recommended for real usage. /// DuplicateChat(DuplicateChatEvent), /// } /// From f36d0fac2f9a2b909190e655db751f30b5f8f051 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 3 Sep 2021 09:29:10 +0300 Subject: [PATCH 053/104] Fix docs --- codegen/shim/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 8fe9fb6..4fc50ec 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -105,6 +105,7 @@ use proc_macro::TokenStream; /// ``` /// /// [`Event`]: arcana_core::es::Event +/// [`Sourced`]: arcana_core::es::event::Sourced /// [`Versioned`]: arcana_core::es::event::Versioned /// [0]: arcana_core::es::Event::name() /// [1]: arcana_core::es::Event::version() From a62e337a73dfcedcb3c15d31acc2e4395dd63349 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 3 Sep 2021 10:57:05 +0300 Subject: [PATCH 054/104] WIP --- examples/chat/src/domain/chat.rs | 39 ++++++++++++++++++++++++++++ examples/chat/src/domain/message.rs | 12 +++++++++ examples/chat/src/domain/mod.rs | 4 +++ examples/chat/src/main.rs | 29 ++++++++++++++++++--- examples/chat/src/storage/chat.rs | 18 ++++++++----- examples/chat/src/storage/message.rs | 13 ++++++++++ examples/chat/src/storage/mod.rs | 30 ++++++++++++++++++--- 7 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 examples/chat/src/domain/chat.rs create mode 100644 examples/chat/src/domain/message.rs create mode 100644 examples/chat/src/domain/mod.rs create mode 100644 examples/chat/src/storage/message.rs diff --git a/examples/chat/src/domain/chat.rs b/examples/chat/src/domain/chat.rs new file mode 100644 index 0000000..140bf84 --- /dev/null +++ b/examples/chat/src/domain/chat.rs @@ -0,0 +1,39 @@ +use arcana::es::event::{Initialized, Sourced}; + +use crate::event; + +#[derive(Debug, Eq, PartialEq)] +pub struct Chat { + pub visibility: Visibility, + pub message_count: usize, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum Visibility { + Private, + Public, +} + +impl Initialized for Chat { + fn init(_: &event::chat::public::Created) -> Self { + Self { + visibility: Visibility::Public, + message_count: 0, + } + } +} + +impl Initialized for Chat { + fn init(_: &event::chat::private::Created) -> Self { + Self { + visibility: Visibility::Private, + message_count: 0, + } + } +} + +impl Sourced for Chat { + fn apply(&mut self, _: &event::message::Posted) { + self.message_count += 1; + } +} diff --git a/examples/chat/src/domain/message.rs b/examples/chat/src/domain/message.rs new file mode 100644 index 0000000..cea349b --- /dev/null +++ b/examples/chat/src/domain/message.rs @@ -0,0 +1,12 @@ +use arcana::es::event::Initialized; + +use crate::event::message::Posted; + +#[derive(Debug, Eq, PartialEq)] +pub struct Message; + +impl Initialized for Message { + fn init(_: &Posted) -> Self { + Self + } +} diff --git a/examples/chat/src/domain/mod.rs b/examples/chat/src/domain/mod.rs new file mode 100644 index 0000000..b9b24af --- /dev/null +++ b/examples/chat/src/domain/mod.rs @@ -0,0 +1,4 @@ +pub mod chat; +pub mod message; + +pub use self::{chat::Chat, message::Message}; diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 404097c..8597b4b 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -1,22 +1,45 @@ #![feature(generic_associated_types)] +mod domain; mod event; mod storage; use std::array; -use arcana::es::EventAdapter as _; +use arcana::es::{event::Sourced, EventAdapter as _}; use futures::{stream, Stream, TryStreamExt as _}; #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::main] async fn main() { - let events = storage::chat::Adapter + let mut chat = Option::::None; + let mut message = Option::::None; + + let chat_events = storage::chat::Adapter .transform_all(incoming_events(), &()) + .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await .unwrap(); - println!("{:?}", events); + println!("{:?}", chat_events); + + assert_eq!( + chat, + Some(domain::Chat { + visibility: domain::chat::Visibility::Public, + message_count: 1 + }), + ); + + let message_events = storage::message::Adapter + .transform_all(incoming_events(), &()) + .inspect_ok(|ev| message.apply(ev)) + .try_collect::>() + .await + .unwrap(); + println!("{:?}", message_events); + + assert_eq!(message, Some(domain::Message)); } fn incoming_events() -> impl Stream { diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 0feb281..2e4c0fc 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,6 +1,9 @@ -use arcana::es::adapter::transformer::{self, strategy}; +use arcana::es::{ + adapter::transformer::{self, strategy}, + event::Sourcing, +}; -use crate::event; +use crate::{domain, event}; #[derive(Debug)] pub struct Adapter; @@ -13,15 +16,18 @@ impl transformer::WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::AsIs; -} - impl transformer::WithStrategy for Adapter { type Strategy = strategy::Initialized>; } +impl transformer::WithStrategy for Adapter +where + Ev: Sourcing, +{ + type Strategy = strategy::AsIs; +} + // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs new file mode 100644 index 0000000..fd6c3aa --- /dev/null +++ b/examples/chat/src/storage/message.rs @@ -0,0 +1,13 @@ +use arcana::es::adapter::transformer::{self, strategy}; + +use crate::event; + +pub struct Adapter; + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 0aea786..2fc4e33 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -1,13 +1,17 @@ pub mod chat; +pub mod message; use std::{any::Any, convert::Infallible}; -use arcana::es::adapter::Transformer; +use arcana::es::{ + self, + adapter::{transformer::strategy, Transformer}, +}; use derive_more::From; use crate::event; -#[derive(Debug, From, Transformer)] +#[derive(Debug, es::Event, From, Transformer)] #[event( transformer( adapter = chat::Adapter, @@ -15,13 +19,19 @@ use crate::event; ctx = dyn Any, err = Infallible, ), + transformer( + adapter = message::Adapter, + into = event::Message, + ctx = dyn Any, + err = Infallible, + ), )] pub enum Event { Chat(ChatEvent), Message(MessageEvent), } -#[derive(Debug, From, Transformer)] +#[derive(Debug, es::Event, From, Transformer)] #[event( transformer( adapter = chat::Adapter, @@ -36,7 +46,7 @@ pub enum ChatEvent { PrivateCreated(event::chat::private::Created), } -#[derive(Debug, From, Transformer)] +#[derive(Debug, es::Event, From, Transformer)] #[event( transformer( adapter = chat::Adapter, @@ -44,7 +54,19 @@ pub enum ChatEvent { ctx = dyn Any, err = Infallible, ), + transformer( + adapter = message::Adapter, + into = event::Message, + ctx = dyn Any, + err = Infallible, + ), )] pub enum MessageEvent { Posted(event::message::Posted), } + +impl From for event::Message { + fn from(u: strategy::Unknown) -> Self { + match u {} + } +} From 7521cb1bc3aeee2e8a7f9e3633193b085c5f8431 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 3 Sep 2021 12:40:46 +0300 Subject: [PATCH 055/104] Add email example to demonstrate strategy::Split --- examples/chat/Cargo.toml | 1 + examples/chat/src/domain/email.rs | 24 +++++++++ examples/chat/src/domain/mod.rs | 3 +- examples/chat/src/event/email.rs | 24 +++++++++ examples/chat/src/event/mod.rs | 7 +++ examples/chat/src/main.rs | 24 +++++++++ examples/chat/src/storage/chat.rs | 4 ++ examples/chat/src/storage/email.rs | 81 ++++++++++++++++++++++++++++ examples/chat/src/storage/message.rs | 4 ++ examples/chat/src/storage/mod.rs | 35 ++++++++++++ 10 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 examples/chat/src/domain/email.rs create mode 100644 examples/chat/src/event/email.rs create mode 100644 examples/chat/src/storage/email.rs diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index 98174f7..dac141e 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -13,5 +13,6 @@ publish = false [dependencies] arcana = { version = "0.1.0-dev", path = "../..", features = ["derive", "es"] } derive_more = "0.99" +either = "1" futures = "0.3" tokio = { version = "1", features = ["full"] } diff --git a/examples/chat/src/domain/email.rs b/examples/chat/src/domain/email.rs new file mode 100644 index 0000000..c6db177 --- /dev/null +++ b/examples/chat/src/domain/email.rs @@ -0,0 +1,24 @@ +use arcana::es::event::{Initialized, Sourced}; + +use crate::event; + +#[derive(Debug, Eq, PartialEq)] +pub struct Email { + pub value: String, + pub confirmed_by: Option, +} + +impl Initialized for Email { + fn init(event: &event::email::Added) -> Self { + Self { + value: event.email.clone(), + confirmed_by: None, + } + } +} + +impl Sourced for Email { + fn apply(&mut self, event: &event::email::Confirmed) { + self.confirmed_by = Some(event.confirmed_by.clone()); + } +} diff --git a/examples/chat/src/domain/mod.rs b/examples/chat/src/domain/mod.rs index b9b24af..d64e19f 100644 --- a/examples/chat/src/domain/mod.rs +++ b/examples/chat/src/domain/mod.rs @@ -1,4 +1,5 @@ pub mod chat; +pub mod email; pub mod message; -pub use self::{chat::Chat, message::Message}; +pub use self::{chat::Chat, email::Email, message::Message}; diff --git a/examples/chat/src/event/email.rs b/examples/chat/src/event/email.rs new file mode 100644 index 0000000..05cd16e --- /dev/null +++ b/examples/chat/src/event/email.rs @@ -0,0 +1,24 @@ +use arcana::es::event; + +#[derive(Debug, event::Versioned)] +#[event(name = "email.added", version = 2)] +pub struct Added { + pub email: String, +} + +#[derive(Debug, event::Versioned)] +#[event(name = "email.confirmed", version = 2)] +pub struct Confirmed { + pub confirmed_by: String, +} + +pub mod v1 { + use super::event; + + #[derive(Debug, event::Versioned)] + #[event(name = "email.added_and_confirmed", version = 1)] + pub struct AddedAndConfirmed { + pub email: String, + pub confirmed_by: Option, + } +} diff --git a/examples/chat/src/event/mod.rs b/examples/chat/src/event/mod.rs index 98d667f..a41d628 100644 --- a/examples/chat/src/event/mod.rs +++ b/examples/chat/src/event/mod.rs @@ -1,4 +1,5 @@ pub mod chat; +pub mod email; pub mod message; use arcana::es::{event, Event}; @@ -11,6 +12,12 @@ pub enum Chat { MessagePosted(message::Posted), } +#[derive(Debug, Event, From)] +pub enum Email { + Added(event::Initial), + Confirmed(email::Confirmed), +} + #[derive(Debug, Event, From)] pub enum Message { MessagePosted(event::Initial), diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 8597b4b..51c4cec 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -14,6 +14,7 @@ use futures::{stream, Stream, TryStreamExt as _}; async fn main() { let mut chat = Option::::None; let mut message = Option::::None; + let mut email = Option::::None; let chat_events = storage::chat::Adapter .transform_all(incoming_events(), &()) @@ -31,6 +32,22 @@ async fn main() { }), ); + let email_events = storage::email::Adapter + .transform_all(incoming_events(), &()) + .inspect_ok(|ev| email.apply(ev)) + .try_collect::>() + .await + .unwrap(); + println!("{:?}", email_events); + + assert_eq!( + email, + Some(domain::Email { + value: "hello@world.com".to_owned(), + confirmed_by: Some("Test".to_owned()), + }) + ); + let message_events = storage::message::Adapter .transform_all(incoming_events(), &()) .inspect_ok(|ev| message.apply(ev)) @@ -49,5 +66,12 @@ fn incoming_events() -> impl Stream { .into(), storage::ChatEvent::PublicCreated(event::chat::public::Created).into(), storage::MessageEvent::Posted(event::message::Posted).into(), + storage::EmailEvent::AddedAndConfirmed( + event::email::v1::AddedAndConfirmed { + email: "hello@world.com".to_owned(), + confirmed_by: Some("Test".to_owned()), + }, + ) + .into(), ])) } diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 2e4c0fc..fc43386 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -21,6 +21,10 @@ impl transformer::WithStrategy for Adapter { strategy::Initialized>; } +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + impl transformer::WithStrategy for Adapter where Ev: Sourcing, diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs new file mode 100644 index 0000000..8ac34a9 --- /dev/null +++ b/examples/chat/src/storage/email.rs @@ -0,0 +1,81 @@ +use std::array; + +use arcana::es::{ + adapter::transformer::{self, strategy}, + event::{Initial, Sourcing}, +}; +use either::Either; + +use crate::{domain, event}; + +pub struct Adapter; + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = + strategy::Split>; +} + +impl + strategy::Splitter< + event::email::v1::AddedAndConfirmed, + Either, + > for Adapter +{ + type Iterator = SplitEmail; + + fn split( + &self, + event: event::email::v1::AddedAndConfirmed, + ) -> Self::Iterator { + use either::{Left, Right}; + + #[allow(clippy::option_if_let_else)] // use of moved value + if let Some(confirmed_by) = event.confirmed_by { + Right(array::IntoIter::new([ + Left(event::email::Added { email: event.email }), + Right(event::email::Confirmed { confirmed_by }), + ])) + } else { + Left(array::IntoIter::new([Left(event::email::Added { + email: event.email, + })])) + } + } +} + +type SplitEmail = Either< + array::IntoIter, 1>, + array::IntoIter, 2>, +>; + +impl From> + for event::Email +{ + fn from(ev: Either) -> Self { + match ev { + Either::Left(ev) => Initial(ev).into(), + Either::Right(ev) => ev.into(), + } + } +} + +impl transformer::WithStrategy for Adapter +where + Ev: Sourcing, +{ + type Strategy = strategy::AsIs; +} diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index fd6c3aa..4c4a065 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -11,3 +11,7 @@ impl transformer::WithStrategy for Adapter { impl transformer::WithStrategy for Adapter { type Strategy = strategy::Initialized; } + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 2fc4e33..30bc438 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -1,4 +1,5 @@ pub mod chat; +pub mod email; pub mod message; use std::{any::Any, convert::Infallible}; @@ -19,6 +20,12 @@ use crate::event; ctx = dyn Any, err = Infallible, ), + transformer( + adapter = email::Adapter, + into = event::Email, + ctx = dyn Any, + err = Infallible, + ), transformer( adapter = message::Adapter, into = event::Message, @@ -29,6 +36,7 @@ use crate::event; pub enum Event { Chat(ChatEvent), Message(MessageEvent), + Email(EmailEvent), } #[derive(Debug, es::Event, From, Transformer)] @@ -65,6 +73,33 @@ pub enum MessageEvent { Posted(event::message::Posted), } +#[derive(Debug, es::Event, From, Transformer)] +#[event( + transformer( + adapter = email::Adapter, + into = event::Email, + ctx = dyn Any, + err = Infallible, + ), +)] +pub enum EmailEvent { + Added(event::email::Added), + Confirmed(event::email::Confirmed), + AddedAndConfirmed(event::email::v1::AddedAndConfirmed), +} + +impl From for event::Chat { + fn from(u: strategy::Unknown) -> Self { + match u {} + } +} + +impl From for event::Email { + fn from(u: strategy::Unknown) -> Self { + match u {} + } +} + impl From for event::Message { fn from(u: strategy::Unknown) -> Self { match u {} From a87105efdbf49ec85ae569582834124d62bc8f2c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 9 Sep 2021 16:15:08 +0300 Subject: [PATCH 056/104] WIP [skip ci] --- Cargo.toml | 6 + codegen/impl/src/es/event/mod.rs | 135 +++++- codegen/impl/src/es/event/transformer.rs | 522 ++++++++++++++--------- core/src/es/adapter/mod.rs | 2 +- core/src/es/adapter/transformer/mod.rs | 250 ++++++++++- examples/adapter.rs | 135 ++++++ examples/chat/src/main.rs | 2 +- examples/chat/src/storage/chat.rs | 38 +- examples/chat/src/storage/email.rs | 44 +- examples/chat/src/storage/message.rs | 30 +- examples/chat/src/storage/mod.rs | 65 +-- examples/specialization.rs | 52 +++ src/es/adapter/mod.rs | 2 +- src/es/adapter/transformer/mod.rs | 9 +- 14 files changed, 960 insertions(+), 332 deletions(-) create mode 100644 examples/adapter.rs create mode 100644 examples/specialization.rs diff --git a/Cargo.toml b/Cargo.toml index 22ed6b0..3096ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,12 @@ derive_more = "0.99" either = "1" futures = "0.3" tokio = { version = "1", features = ["full"] } +pin-project = "1" +static_assertions = "1" + +[[example]] +name = "adapter" +required-features = ["es", "derive"] [package.metadata.docs.rs] all-features = true diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 605abb0..c68ba7a 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -3,7 +3,7 @@ pub mod transformer; pub mod versioned; -use std::convert::TryFrom; +use std::{convert::TryFrom, iter}; use proc_macro2::TokenStream; use quote::quote; @@ -36,7 +36,12 @@ pub struct VariantAttrs { /// /// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))] +#[to_tokens(append( + impl_event, + impl_event_sourced, + unpack_enum_impl, + gen_uniqueness_glue_code +))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, @@ -229,6 +234,58 @@ impl Definition { } } + /// TODO + #[must_use] + pub fn unpack_enum_impl(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + let path = + quote! { ::arcana::es::adapter::transformer::specialization }; + + let variants_len = self.variants.len(); + let var_ty = self + .variants + .iter() + .flat_map(|v| &v.fields) + .map(|f| &f.ty) + .collect::>(); + let variants_matrix = + self.variants.iter().enumerate().map(|(i, var)| { + let before_none = iter::repeat(quote! { None }).take(i); + let after_none = + iter::repeat(quote! { None }).take(variants_len - i - 1); + let var_ident = &var.ident; + + quote! { + Self::#var_ident(e) => { + ( #( #before_none, )* Some(e), #( #after_none ),* ) + }, + } + }); + let unreachable_arm = self.has_ignored_variants.then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + #[automatically_derived] + impl #impl_gens #path::UnpackEnum for #ty#ty_gens #where_clause { + const TUPLE_SIZE: usize = #variants_len; + + #[allow(clippy::type_complexity)] + type Tuple = ( + #( Option<#var_ty>, )* + ); + + fn unpack(self) -> Self::Tuple { + match self { + #( #variants_matrix )* + #unreachable_arm + } + } + } + } + } + /// Generates hidden machinery code used to statically check that all the /// [`Event::name`][0]s and [`Event::version`][1]s pairs are corresponding /// to a single Rust type. @@ -369,6 +426,30 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::es::adapter::transformer::specialization::UnpackEnum + for Event + { + const TUPLE_SIZE: usize = 2usize; + + #[allow(clippy::type_complexity)] + type Tuple = ( + Option, + Option + ); + + fn unpack(self) -> Self::Tuple { + match self { + Self::File(e) => { + (Some(e), None) + }, + Self::Chat(e) => { + (None, Some(e),) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { @@ -493,6 +574,31 @@ mod spec { } } + #[automatically_derived] + impl<'a, F, C> + ::arcana::es::adapter::transformer::specialization::UnpackEnum + for Event<'a, F, C> + { + const TUPLE_SIZE: usize = 2usize; + + #[allow(clippy::type_complexity)] + type Tuple = ( + Option >, + Option > + ); + + fn unpack(self) -> Self::Tuple { + match self { + Self::File(e) => { + (Some(e), None) + }, + Self::Chat(e) => { + (None, Some(e),) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl<'a, F, C> ::arcana::es::event::codegen::Versioned @@ -631,6 +737,31 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::es::adapter::transformer::specialization::UnpackEnum + for Event + { + const TUPLE_SIZE: usize = 2usize; + + #[allow(clippy::type_complexity)] + type Tuple = ( + Option, + Option + ); + + fn unpack(self) -> Self::Tuple { + match self { + Self::File(e) => { + (Some(e), None) + }, + Self::Chat(e) => { + (None, Some(e),) + }, + _ => unreachable!(), + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index abde309..356f192 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -1,10 +1,16 @@ //! `#[derive(adapter::Transformer)]` macro implementation. -use std::{convert::TryFrom, iter}; +use std::{convert::TryFrom, iter, num::NonZeroUsize}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::spanned::Spanned; +use std::ops::Deref; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, +}; use synthez::{ParseAttrs, Required, Spanning, ToTokens}; /// Expands `#[derive(adapter::Transformer)]` macro. @@ -38,11 +44,9 @@ pub struct Attrs { /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug, Default, ParseAttrs)] pub struct InnerAttrs { - /// Type to derive [`Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - #[parse(value)] - pub adapter: Required, + /// TODO + #[parse(value, alias = from)] + pub event: Vec, /// [`Transformer::Transformed`][0] type. /// @@ -61,32 +65,159 @@ pub struct InnerAttrs { /// [0]: arcana_core::es::adapter::Transformer::Error #[parse(value, alias = err)] pub error: Required, + + /// TODO + #[parse(value, alias = ver, validate = can_parse_as_non_zero_usize)] + pub number_of_events: Option, } -impl From for ImplDefinition { - fn from(attrs: InnerAttrs) -> Self { +/// Checks whether the given `value` can be parsed as [`NonZeroUsize`]. +fn can_parse_as_non_zero_usize<'a>( + val: impl Into>, +) -> syn::Result<()> { + val.into() + .map(syn::LitInt::base10_parse::) + .transpose() + .map(drop) +} + +impl InnerAttrs { + fn into_impl_definition( + self, + ) -> impl Iterator> { let InnerAttrs { - adapter, + event, transformed, context, error, - } = attrs; - Self { - adapter: adapter.into_inner(), - transformed: transformed.into_inner(), - context: context.into_inner(), - error: error.into_inner(), - } + number_of_events, + } = self; + + event.into_iter().map(move |ev| { + let event = ev + .event + .as_ref() + .ok_or_else(|| syn::Error::new(Span::call_site(), "todo"))?; + let num = ev + .number_of_events + .as_ref() + .xor(number_of_events.as_ref()) + .ok_or_else(|| { + let span = if let (Some(l), Some(r)) = ( + ev.number_of_events.as_ref(), + number_of_events.as_ref(), + ) { + l.span().join(r.span()) + } else { + None + }; + + syn::Error::new( + span.unwrap_or_else(|| event.span()), + "exactly 1 'number_of_events' attribute expected", + ) + })?; + + Ok(ImplDefinition { + event: event.clone(), + transformed: transformed.deref().clone(), + context: context.deref().clone(), + error: error.deref().clone(), + number_of_events: num.base10_parse()?, + }) + }) } } // TODO: add PartialEq impls in synthez impl PartialEq for InnerAttrs { fn eq(&self, other: &Self) -> bool { - *self.adapter == *other.adapter + *self.event == *other.event && *self.transformed == *other.transformed && *self.context == *other.context && *self.error == *other.error + && self.number_of_events == other.number_of_events + } +} + +/// TODO +#[derive(Debug, Default)] +pub struct EventAttrs { + event: Option, + number_of_events: Option, +} + +#[allow(dead_code)] +struct EventAttrsParse { + event: syn::Type, + comma: Option, + ident: Option, + equal: Option, + number: Option, +} + +impl Parse for EventAttrs { + fn parse(input: ParseStream<'_>) -> syn::Result { + input + .peek(token::Paren) + .then(|| { + let content; + syn::parenthesized!(content in input); + Ok(content) + }) + .transpose()? + .map_or_else( + || { + Ok(EventAttrs { + event: Some(input.parse::()?), + number_of_events: None, + }) + }, + |input| { + let parsed = EventAttrsParse { + event: input.parse()?, + comma: input.parse()?, + ident: input.parse()?, + equal: input.parse()?, + number: input.parse()?, + }; + + if let Some(ident) = &parsed.ident { + if ident != "number_of_events" { + return Err(syn::Error::new( + parsed.ident.span(), + "expected number_of_events", + )); + } + } + + Ok(EventAttrs { + event: Some(parsed.event), + number_of_events: parsed.number, + }) + }, + ) + } +} + +impl ToTokens for EventAttrs { + fn to_tokens(&self, tokens: &mut TokenStream) { + let event = &self.event; + let attr = if let Some(num) = &self.number_of_events { + quote! { (#event, number_of_events = #num) } + } else { + quote! { #event } + }; + + attr.to_tokens(tokens); + } +} + +// TODO: add PartialEq impls in synthez +impl PartialEq for EventAttrs { + fn eq(&self, other: &Self) -> bool { + self.event == other.event + && self.number_of_events == other.number_of_events } } @@ -97,18 +228,12 @@ impl PartialEq for InnerAttrs { #[derive(Debug, ToTokens)] #[to_tokens(append(derive_transformer))] pub struct Definition { - /// Generic parameter of the [`Transformer`][0]. - /// - /// [0]: arcana_core::es::adapter::Transformer - pub event: syn::Ident, + /// TODO + pub adapter: syn::Ident, /// [`syn::Generics`] of this enum's type. pub generics: syn::Generics, - /// [`struct@syn::Ident`] and single [`syn::Type`] of every - /// [`syn::FieldsUnnamed`] [`syn::Variant`]. - pub variants: Vec<(syn::Ident, syn::Type)>, - /// Definitions of structures to derive [`Transformer`][0] on. /// /// [0]: arcana_core::es::adapter::Transformer @@ -121,10 +246,8 @@ pub struct Definition { /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug)] pub struct ImplDefinition { - /// Type to derive [`Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - pub adapter: syn::Type, + /// TODO + pub event: syn::Type, /// [`Transformer::Transformed`][0] type. /// @@ -140,13 +263,15 @@ pub struct ImplDefinition { /// /// [0]: arcana_core::es::adapter::Transformer::Error pub error: syn::Type, + + /// TODO + pub number_of_events: NonZeroUsize, } impl TryFrom for Definition { type Error = syn::Error; fn try_from(input: syn::DeriveInput) -> syn::Result { - let span = input.span(); let attrs: Attrs = Attrs::parse_attrs("event", &input)?; if attrs.transformer.is_empty() { @@ -156,105 +281,94 @@ impl TryFrom for Definition { )); } - let data = if let syn::Data::Enum(data) = input.data { - data - } else { - return Err(syn::Error::new(input.span(), "expected enum only")); - }; - - let variants = data - .variants - .into_iter() - .map(Self::parse_variant) - .collect::>>()?; - if variants.is_empty() { - return Err(syn::Error::new( - span, - "enum must have at least one variant", - )); - } - let transformers = attrs .transformer .into_iter() - .map(|tr| tr.into_inner().into()) - .collect(); + .flat_map(|tr| tr.into_inner().into_impl_definition()) + .collect::>()?; Ok(Self { - event: input.ident, + adapter: input.ident, generics: input.generics, - variants, transformers, }) } } impl Definition { - /// Parses [`syn::Variant`], returning its [`syn::Ident`] and single inner - /// [`syn::Field`]. - /// - /// # Errors - /// - /// If [`syn::Variant`] doesn't have exactly one unnamed 1 [`syn::Field`]. - fn parse_variant( - variant: syn::Variant, - ) -> syn::Result<(syn::Ident, syn::Type)> { - if variant.fields.len() != 1 { - return Err(syn::Error::new( - variant.span(), - "enum variants must have exactly 1 field", - )); - } - if !matches!(variant.fields, syn::Fields::Unnamed(_)) { - return Err(syn::Error::new( - variant.span(), - "only tuple struct enum variants allowed", - )); - } - - Ok((variant.ident, variant.fields.into_iter().next().unwrap().ty)) - } - /// Generates code to derive [`Transformer`][0] trait. /// /// [0]: arcana_core::es::adapter::Transformer #[must_use] pub fn derive_transformer(&self) -> TokenStream { - let event = &self.event; + let adapter = &self.adapter; self.transformers.iter().map(|tr| { let ImplDefinition { - adapter, + event, transformed, context, error, + number_of_events, } = tr; - let inner_match = self.inner_match(adapter); - let transformed_stream = self.transformed_stream(adapter); + let inner_match = self.inner_match(transformed, *number_of_events); let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); + let number_of_events = number_of_events.get(); + let codegen_path = quote! { ::arcana::es::adapter::codegen }; + let specialization_path = quote! { + arcana::es::adapter::transformer::specialization + }; + + quote! { + ::arcana::es::adapter::transformer::wrong_number_of_events!( + #number_of_events == + <#event as #specialization_path::UnpackEnum>::TUPLE_SIZE, + ); + #[automatically_derived] - impl #impl_gen ::arcana::es::adapter::Transformer< - #event#type_gen - > for #adapter #where_clause + impl #impl_gen ::arcana::es::adapter::Transformer<#event> for + #adapter#type_gen #where_clause { type Context = #context; type Error = #error; type Transformed = #transformed; - type TransformedStream<'me, 'ctx> = #transformed_stream; + #[allow(clippy::type_complexity)] + type TransformedStream<'me, 'ctx> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn #codegen_path::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + > + >; + #[allow( + clippy::too_many_lines, + clippy::unused_self, + clippy::needless_borrow + )] fn transform<'me, 'ctx>( &'me self, - __event: #event, - __context: - &'ctx >::Context, - ) -> >:: - TransformedStream<'me, 'ctx> - { - match __event { + event: #event, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'me, 'ctx> { + #[allow(unused_imports)] + use #specialization_path::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByEmpty as _, + Wrap, + }; + + match #specialization_path::UnpackEnum::unpack(event) { #inner_match } } @@ -264,131 +378,117 @@ impl Definition { .collect() } - /// Generates code of [`Transformer::Transformed`][0] associated type. - /// - /// This is basically a recursive type - /// [`Either`]`>`, where every `VarN` is an - /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a - /// [`stream::Map`] with a function that uses [`From`] impl to transform - /// [`Event`]s into compatible ones. - /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - /// [1]: arcana_core::es::adapter::Transformer::TransformedStream - /// [`Either`]: futures::future::Either - /// [`Event`]: trait@::arcana_core::es::Event - /// [`stream::Map`]: futures::stream::Map + /// TODO #[must_use] - pub fn transformed_stream(&self, adapter: &syn::Type) -> TokenStream { - let from = &self.event; + pub fn inner_match( + &self, + transformed: &syn::Type, + number_of_events: NonZeroUsize, + ) -> TokenStream { + let number_of_events = number_of_events.get(); + let adapter = &self.adapter; + + let matches = (0..).take(number_of_events).map(|i| { + let before_none = iter::repeat(quote! { None }).take(i); + let after_none = + iter::repeat(quote! { None }).take(number_of_events - i - 1); + + let assert_fn = Self::assert_impl_any( + &syn::Ident::new("event", Span::call_site()), + [ + parse_quote! { ::arcana::es::event::Versioned }, + parse_quote! { + ::arcana::es::adapter::TransformedBy<#adapter> + }, + ], + ); - let transformed_stream = |event: &syn::Type| { quote! { - ::arcana::es::adapter::codegen::futures::stream::Map< - <#adapter as ::arcana::es::adapter::Transformer<#event >>:: - TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - <#adapter as ::arcana::es::adapter:: - Transformer<#event >>::Transformed, - <#adapter as ::arcana::es::adapter:: - Transformer<#event >>::Error, - >, - ) -> ::std::result::Result< - <#adapter as ::arcana::es::adapter:: - Transformer<#from>>::Transformed, - <#adapter as ::arcana::es::adapter:: - Transformer<#from>>::Error, - >, - > + ( #( #before_none, )* Some(event), #( #after_none ),* ) => { + let check = #assert_fn; + let event = check(); + + ::std::boxed::Box::pin( + (&&&&&Wrap::<&#adapter, _, #transformed>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), + ) + } } - }; + }); - self.variants - .iter() - .rev() - .fold(None, |acc, (_, var_ty)| { - let variant_stream = transformed_stream(var_ty); - Some( - acc.map(|acc| { - quote! { - ::arcana::es::adapter::codegen::futures::future:: - Either< - #variant_stream, - #acc, - > - } - }) - .unwrap_or(variant_stream), - ) - }) - .unwrap_or_default() + quote! { + #( #matches )* + _ => unreachable!(), + } } - /// Generates code for implementation of a [`Transformer::transform()`][0] - /// fn. - /// - /// Generated code matches over every [`Event`]'s variant and makes it - /// compatible with [`Self::transformed_stream()`] type with - /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] - /// combinators. - /// - /// [0]: arcana_core::es::adapter::Transformer::transform - /// [`Event`]: trait@arcana_core::es::Event - /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() - /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() + /// TODO #[must_use] - pub fn inner_match(&self, adapter: &syn::Type) -> TokenStream { - let event = &self.event; + pub fn assert_impl_any( + value: &syn::Ident, + traits: impl AsRef<[syn::Type]>, + ) -> TokenStream { + let traits = traits.as_ref().iter(); - self.variants - .iter() - .enumerate() - .map(|(i, (variant_ident, variant_ty))| { - let stream_map = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt::map( - <#adapter as ::arcana::es::adapter::Transformer< - #variant_ty - > >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) - }; - - let right_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - right_stream - }; - let left_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - left_stream - }; - let left_stream_count = - (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); - - let transformed_stream = iter::repeat(left_stream) - .take(left_stream_count) - .chain(iter::repeat(right_stream).take(i)) - .fold(stream_map, |acc, stream| { - quote! { #stream(#acc) } - }); - - quote! { - #event::#variant_ident(__event) => { - #transformed_stream - }, - } - }) - .collect() + quote! { + || { + struct AssertImplAnyFallback; + + struct ActualAssertImplAnyToken; + trait AssertImplAnyToken {} + impl AssertImplAnyToken for ActualAssertImplAnyToken {} + + fn assert_impl_any_token(_: T) + where T: AssertImplAnyToken {} + + let previous = AssertImplAnyFallback; + + #( let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: #traits, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&#value, previous) + }; )* + + assert_impl_any_token( + previous._static_assertions_impl_any(), + ); + + #value + } + } } } diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 778fba2..a40fde4 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -12,7 +12,7 @@ use futures::{future, stream, Stream, StreamExt as _}; use pin_project::pin_project; #[doc(inline)] -pub use self::transformer::Transformer; +pub use self::transformer::{TransformedBy, Transformer}; /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 299652d..1a2703d 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -56,9 +56,255 @@ pub trait Transformer { /// Instead of implementing [`Transformer`] manually, you can use this trait /// with some [`Strategy`]. -pub trait WithStrategy { +pub trait WithStrategy: Sized { /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event - type Strategy; + type Strategy: Strategy; +} + +/// TODO +pub trait TransformedBy {} + +impl TransformedBy for Ev where A: Transformer {} + +pub mod specialization { + //! TODO + + #![allow(clippy::unused_self)] + + use std::marker::PhantomData; + + use crate::es::{ + adapter::{ + transformer::{strategy, WithStrategy}, + Transformer, + }, + event, + }; + use futures::{future, stream, StreamExt as _}; + + /// TODO + pub trait UnpackEnum { + /// TODO + const TUPLE_SIZE: usize; + + /// TODO + type Tuple; + + /// TODO + fn unpack(self) -> Self::Tuple; + } + + /// TODO + #[derive(Debug)] + pub struct Wrap( + /// TODO + pub Adapter, + /// TODO + pub Event, + /// TODO + pub PhantomData, + ); + + // With Skip Adapter + + /// TODO + pub trait TransformedBySkipAdapter { + /// TODO + fn get_tag(&self) -> AdapterSkippedTag; + } + + impl TransformedBySkipAdapter + for &&&&Wrap<&Adapter, &Event, TransformedEvent> + where + Adapter: WithStrategy, + { + fn get_tag(&self) -> AdapterSkippedTag { + AdapterSkippedTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct AdapterSkippedTag; + + impl AdapterSkippedTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + _: Event, + _: &Ctx, + ) -> stream::Empty> + where + Ctx: ?Sized, + { + stream::empty() + } + } + + // With Adapter + + /// TODO + pub trait TransformedByAdapter { + /// TODO + fn get_tag(&self) -> AdapterTag; + } + + impl TransformedByAdapter + for &&&Wrap<&Adapter, &Event, TransformedEvent> + where + Adapter: Transformer, + { + fn get_tag(&self) -> AdapterTag { + AdapterTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct AdapterTag; + + impl AdapterTag { + /// TODO + pub fn transform_event<'me, 'ctx, Adapter, Event, TrEvent, Ctx, Err>( + self, + adapter: &'me Adapter, + ev: Event, + context: &'ctx Ctx, + ) -> AdapterTagStream<'me, 'ctx, Adapter, Event, TrEvent, Err> + where + Event: 'static, + Ctx: ?Sized, + Adapter: Transformer, + TrEvent: From, + Err: From, + { + >::transform(adapter, ev, context) + .map(|res| res.map(Into::into).map_err(Into::into)) + } + } + + type AdapterTagStream<'me, 'ctx, Adapter, Event, TrEvent, Err> = + stream::Map< + >::TransformedStream<'me, 'ctx>, + fn( + Result< + >::Transformed, + >::Error, + >, + ) -> Result, + >; + + // With From + + /// TODO + pub trait TransformedByFrom { + /// TODO + fn get_tag(&self) -> FromTag; + } + + impl TransformedByFrom + for &&Wrap<&Adapter, &Event, TransformedEvent> + where + TransformedEvent: From, + { + fn get_tag(&self) -> FromTag { + FromTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct FromTag; + + impl FromTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + ev: Event, + _: &Ctx, + ) -> stream::Once>> + where + Ctx: ?Sized, + TrEvent: From, + { + stream::once(future::ready(Ok(ev.into()))) + } + } + + // With From Initial + + /// TODO + pub trait TransformedByFromInitial { + /// TODO + fn get_tag(&self) -> FromInitialTag; + } + + impl TransformedByFromInitial + for &Wrap<&Adapter, &Event, TransformedEvent> + where + TransformedEvent: From>, + { + fn get_tag(&self) -> FromInitialTag { + FromInitialTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct FromInitialTag; + + impl FromInitialTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + ev: Event, + _: &Ctx, + ) -> stream::Once>> + where + Ctx: ?Sized, + TrEvent: From>, + { + stream::once(future::ready(Ok(event::Initial(ev).into()))) + } + } + + // Skip + + /// TODO + pub trait TransformedByEmpty { + /// TODO + fn get_tag(&self) -> EmptyTag; + } + + impl TransformedByEmpty + for Wrap<&Adapter, &Event, TransformedEvent> + { + fn get_tag(&self) -> EmptyTag { + EmptyTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct EmptyTag; + + impl EmptyTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + _: Event, + _: &Ctx, + ) -> stream::Empty> + where + Ctx: ?Sized, + { + stream::empty() + } + } } diff --git a/examples/adapter.rs b/examples/adapter.rs new file mode 100644 index 0000000..707321f --- /dev/null +++ b/examples/adapter.rs @@ -0,0 +1,135 @@ +#![feature(generic_associated_types)] +#![feature(more_qualified_paths)] + +use std::{any::Any, array, convert::Infallible}; + +use arcana::es::{ + adapter::{ + transformer::{self, strategy}, + Adapter, Transformer, + }, + event, Event, +}; +use derive_more::From; +use futures::{stream, TryStreamExt as _}; + +#[tokio::main] +async fn main() { + let ctx = 1_usize; // Can be any type in this example. + let events = stream::iter::<[InputEmailEvents; 5]>([ + InnerInputEvents::from(EmailConfirmed { + confirmed_by: "1".to_string(), + }) + .into(), + InnerInputEvents::from(EmailAdded { + email: "2".to_string(), + }) + .into(), + InnerInputEvents::from(EmailAddedAndConfirmed { + email: "3".to_string(), + confirmed_by: "3".to_string(), + }) + .into(), + SkippedEvent.into(), + Custom.into(), + ]); + + let collect = EmailAdapter + .transform_all(events, &ctx) + .try_collect::>() + .await + .unwrap(); + + println!("context: {}\nevents: {:?}", ctx, collect); +} + +// Individual events + +#[derive(Debug, event::Versioned)] +#[event(name = "custom", version = 1)] +struct Custom; + +#[derive(Debug, event::Versioned)] +#[event(name = "skipped", version = 1)] +struct SkippedEvent; + +#[derive(Debug, event::Versioned)] +#[event(name = "email.added_and_confirmed", version = 1)] +struct EmailAddedAndConfirmed { + email: String, + confirmed_by: String, +} + +#[derive(Debug, event::Versioned)] +#[event(name = "email.added", version = 1)] +struct EmailAdded { + email: String, +} + +#[derive(Debug, event::Versioned)] +#[event(name = "email.confirmed", version = 1)] +struct EmailConfirmed { + confirmed_by: String, +} + +// Input events enum + +#[derive(Debug, Event, From)] +enum InputEmailEvents { + Custom(Custom), + Skipped(SkippedEvent), + Inner(InnerInputEvents), +} + +#[derive(Debug, Event, From)] +enum InnerInputEvents { + AddedAndConfirmed(EmailAddedAndConfirmed), + Added(EmailAdded), + Confirmed(EmailConfirmed), +} + +// Output events enum + +#[derive(Debug, Event, From)] +enum EmailAddedOrConfirmed { + Added(EmailAdded), + Confirmed(event::Initial), +} + +// Adapter + +#[derive(Transformer)] +#[event( + transformer( + from(InputEmailEvents, InnerInputEvents), + into = EmailAddedOrConfirmed, + ctx = dyn Any, + err = Infallible, + number_of_events = 3, + ), +)] +struct EmailAdapter; + +impl transformer::WithStrategy for EmailAdapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for EmailAdapter { + type Strategy = strategy::Split; +} + +impl strategy::Splitter + for EmailAdapter +{ + type Iterator = array::IntoIter; + + fn split(&self, ev: EmailAddedAndConfirmed) -> Self::Iterator { + array::IntoIter::new([ + EmailAdded { email: ev.email }.into(), + event::Initial(EmailConfirmed { + confirmed_by: ev.confirmed_by, + }) + .into(), + ]) + } +} diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 51c4cec..b8a24e0 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -28,7 +28,7 @@ async fn main() { chat, Some(domain::Chat { visibility: domain::chat::Visibility::Public, - message_count: 1 + message_count: 1, }), ); diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index fc43386..cee8811 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,11 +1,26 @@ -use arcana::es::{ - adapter::transformer::{self, strategy}, - event::Sourcing, -}; +use std::{any::Any, convert::Infallible}; -use crate::{domain, event}; +use arcana::es::adapter::{ + transformer::{self, strategy}, + Transformer, +}; -#[derive(Debug)] +use crate::event; + +#[derive(Debug, Transformer)] +#[event( + transformer( + from( + (super::Event, number_of_events = 3), + (super::ChatEvent, number_of_events = 3), + (super::MessageEvent, number_of_events = 1), + (super::EmailEvent, number_of_events = 3), + ), + into = event::Chat, + context = dyn Any, + error = Infallible, + ), +)] pub struct Adapter; impl transformer::WithStrategy for Adapter { @@ -21,17 +36,6 @@ impl transformer::WithStrategy for Adapter { strategy::Initialized>; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter -where - Ev: Sourcing, -{ - type Strategy = strategy::AsIs; -} - // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 8ac34a9..3b4a055 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,27 +1,32 @@ -use std::array; +use std::{any::Any, array, convert::Infallible}; use arcana::es::{ - adapter::transformer::{self, strategy}, - event::{Initial, Sourcing}, + adapter::{ + transformer::{self, strategy}, + Transformer, + }, + event::Initial, }; use either::Either; -use crate::{domain, event}; +use crate::event; +#[derive(Debug, Transformer)] +#[event( + transformer( + from( + (super::Event, number_of_events = 3), + (super::MessageEvent, number_of_events = 1), + (super::ChatEvent, number_of_events = 3), + (super::EmailEvent, number_of_events = 3), + ), + into = event::Email, + context = dyn Any, + error = Infallible, + ), +)] pub struct Adapter; -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} - impl transformer::WithStrategy for Adapter { @@ -72,10 +77,3 @@ impl From> } } } - -impl transformer::WithStrategy for Adapter -where - Ev: Sourcing, -{ - type Strategy = strategy::AsIs; -} diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 4c4a065..682009d 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,17 +1,21 @@ -use arcana::es::adapter::transformer::{self, strategy}; +use std::{any::Any, convert::Infallible}; + +use arcana::es::adapter::Transformer; use crate::event; +#[derive(Debug, Transformer)] +#[event( + transformer( + from( + (super::Event, number_of_events = 3), + (super::MessageEvent, number_of_events = 1), + (super::ChatEvent, number_of_events = 3), + (super::EmailEvent, number_of_events = 3), + ), + into = event::Message, + context = dyn Any, + error = Infallible, + ), +)] pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 30bc438..2ed56ab 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -2,86 +2,31 @@ pub mod chat; pub mod email; pub mod message; -use std::{any::Any, convert::Infallible}; - -use arcana::es::{ - self, - adapter::{transformer::strategy, Transformer}, -}; +use arcana::es::{self, adapter::transformer::strategy}; use derive_more::From; use crate::event; -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = email::Adapter, - into = event::Email, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = message::Adapter, - into = event::Message, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum Event { Chat(ChatEvent), Message(MessageEvent), Email(EmailEvent), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), PublicCreated(event::chat::public::Created), PrivateCreated(event::chat::private::Created), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = message::Adapter, - into = event::Message, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum MessageEvent { Posted(event::message::Posted), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = email::Adapter, - into = event::Email, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum EmailEvent { Added(event::email::Added), Confirmed(event::email::Confirmed), diff --git a/examples/specialization.rs b/examples/specialization.rs new file mode 100644 index 0000000..c773177 --- /dev/null +++ b/examples/specialization.rs @@ -0,0 +1,52 @@ +use futures::{ + future, + stream::{self, LocalBoxStream, StreamExt}, +}; + +struct Event; + +trait TransformedByDefault { + fn transform(self, adapter: Adapter) -> LocalBoxStream<'static, Event>; +} + +impl TransformedByDefault for &Ev { + fn transform(self, _: Adapter) -> LocalBoxStream<'static, Event> { + stream::empty().boxed_local() + } +} + +trait TransformedBy { + fn transform(self, adapter: Adapter) -> LocalBoxStream<'static, Event>; +} + +struct A; + +struct Skipped; + +struct Once; + +impl TransformedBy for Once { + fn transform(self, _: A) -> LocalBoxStream<'static, Event> { + stream::once(future::ready(Event)).boxed_local() + } +} + +#[tokio::main] +async fn main() { + trait Test {} + + impl Test for A {} + + let once = Once; + let skipped = Skipped; + + assert_eq!(once.transform(A).collect::>().await.len(), 1); + assert_eq!(skipped.transform(A).collect::>().await.len(), 0); +} + +trait Foo {} +trait MarkerFoo {} + +impl Foo for T {} + +impl Foo for A {} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index b59bda7..b65823b 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -3,7 +3,7 @@ pub mod transformer; #[doc(inline)] -pub use self::transformer::Transformer; +pub use self::transformer::{TransformedBy, Transformer}; #[doc(inline)] pub use arcana_core::es::adapter::{Adapter, TransformedStream}; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index 657ffc4..91a12b0 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -6,8 +6,15 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; +pub use arcana_core::es::adapter::transformer::{ + specialization, TransformedBy, Transformer, WithStrategy, +}; #[cfg(feature = "derive")] #[doc(inline)] pub use arcana_codegen::es::transformer::Transformer; +#[cfg(feature = "derive")] +#[doc(hidden)] +// TODO: Replace with panic once `const_panic` is stabilized. +// https://github.com/rust-lang/rust/issues/51999 +pub use arcana_codegen::sa::const_assert as wrong_number_of_events; From 0282f9aaa71ecf5d1e7639ed6b9618a0b3003ac8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 10 Sep 2021 16:26:30 +0300 Subject: [PATCH 057/104] WIP [skip ci] --- codegen/impl/Cargo.toml | 1 + codegen/impl/src/es/event/mod.rs | 65 +++--- codegen/impl/src/es/event/transformer.rs | 265 +++++++---------------- core/src/es/adapter/transformer/mod.rs | 14 +- core/src/es/event.rs | 15 ++ examples/adapter.rs | 1 - examples/chat/src/main.rs | 3 + examples/chat/src/storage/chat.rs | 16 +- examples/chat/src/storage/email.rs | 8 +- examples/chat/src/storage/message.rs | 8 +- src/es/adapter/transformer/mod.rs | 2 +- 11 files changed, 147 insertions(+), 251 deletions(-) diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 574fcc0..e5be615 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -17,6 +17,7 @@ readme = "README.md" doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] +either = "1.6.1" proc-macro2 = { version = "1.0.4", default-features = false } quote = { version = "1.0.9", default-features = false } syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index c68ba7a..d4bbcf7 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -39,7 +39,7 @@ pub struct VariantAttrs { #[to_tokens(append( impl_event, impl_event_sourced, - unpack_enum_impl, + get_impl, gen_uniqueness_glue_code ))] pub struct Definition { @@ -236,53 +236,46 @@ impl Definition { /// TODO #[must_use] - pub fn unpack_enum_impl(&self) -> TokenStream { + pub fn get_impl(&self) -> TokenStream { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let path = + let spec_path = quote! { ::arcana::es::adapter::transformer::specialization }; - let variants_len = self.variants.len(); - let var_ty = self + let (id, (var_ident, var_ty)): (Vec<_>, (Vec<_>, Vec<_>)) = self .variants .iter() - .flat_map(|v| &v.fields) - .map(|f| &f.ty) - .collect::>(); - let variants_matrix = - self.variants.iter().enumerate().map(|(i, var)| { - let before_none = iter::repeat(quote! { None }).take(i); - let after_none = - iter::repeat(quote! { None }).take(variants_len - i - 1); - let var_ident = &var.ident; - - quote! { - Self::#var_ident(e) => { - ( #( #before_none, )* Some(e), #( #after_none ),* ) - }, - } - }); - let unreachable_arm = self.has_ignored_variants.then(|| { - quote! { _ => unreachable!(), } - }); + .flat_map(|v| v.fields.iter().zip(iter::once(&v.ident))) + .enumerate() + .map(|(id, (field, ident))| (id, (ident, &field.ty))) + .unzip(); + let number_of_variants = id.len(); quote! { - #[automatically_derived] - impl #impl_gens #path::UnpackEnum for #ty#ty_gens #where_clause { - const TUPLE_SIZE: usize = #variants_len; + impl#impl_gens #spec_path::EnumSize for #ty#ty_gens + #where_clause + { + const SIZE: usize = #number_of_variants; + } - #[allow(clippy::type_complexity)] - type Tuple = ( - #( Option<#var_ty>, )* - ); + #( impl#impl_gens #spec_path::Get<#id> for #ty#ty_gens #where_clause + { + type Out = #var_ty; - fn unpack(self) -> Self::Tuple { - match self { - #( #variants_matrix )* - #unreachable_arm + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::#var_ident(v) = self { + return Some(v); } + None } - } + + fn unwrap(self) -> Self::Out { + if let Self::#var_ident(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } )* } } diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index 356f192..1cf75c7 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -1,16 +1,12 @@ //! `#[derive(adapter::Transformer)]` macro implementation. -use std::{convert::TryFrom, iter, num::NonZeroUsize}; +use std::{convert::TryFrom, iter}; +use either::Either; use proc_macro2::{Span, TokenStream}; use quote::quote; -use std::ops::Deref; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned, - token, -}; +use std::{num::NonZeroUsize, ops::Deref}; +use syn::{parse_quote, spanned::Spanned}; use synthez::{ParseAttrs, Required, Spanning, ToTokens}; /// Expands `#[derive(adapter::Transformer)]` macro. @@ -46,7 +42,7 @@ pub struct Attrs { pub struct InnerAttrs { /// TODO #[parse(value, alias = from)] - pub event: Vec, + pub event: Vec, /// [`Transformer::Transformed`][0] type. /// @@ -67,8 +63,8 @@ pub struct InnerAttrs { pub error: Required, /// TODO - #[parse(value, alias = ver, validate = can_parse_as_non_zero_usize)] - pub number_of_events: Option, + #[parse(value, alias = max, validate = can_parse_as_non_zero_usize)] + pub max_number_of_variants: Option, } /// Checks whether the given `value` can be parsed as [`NonZeroUsize`]. @@ -90,42 +86,30 @@ impl InnerAttrs { transformed, context, error, - number_of_events, + max_number_of_variants, } = self; - event.into_iter().map(move |ev| { - let event = ev - .event - .as_ref() - .ok_or_else(|| syn::Error::new(Span::call_site(), "todo"))?; - let num = ev - .number_of_events - .as_ref() - .xor(number_of_events.as_ref()) - .ok_or_else(|| { - let span = if let (Some(l), Some(r)) = ( - ev.number_of_events.as_ref(), - number_of_events.as_ref(), - ) { - l.span().join(r.span()) - } else { - None - }; - - syn::Error::new( - span.unwrap_or_else(|| event.span()), - "exactly 1 'number_of_events' attribute expected", - ) - })?; + if event.is_empty() { + return Either::Left(iter::once(Err(syn::Error::new( + transformed.span(), + "expected at least 1 `event` or `from` attribute", + )))); + } + Either::Right(event.into_iter().map(move |ev| { Ok(ImplDefinition { - event: event.clone(), + event: ev, transformed: transformed.deref().clone(), context: context.deref().clone(), error: error.deref().clone(), - number_of_events: num.base10_parse()?, + max_number_of_variants: max_number_of_variants + .as_ref() + .map_or(Ok(Definition::MAX_NUMBER_OF_VARIANTS), |max| { + max.base10_parse::() + .map(NonZeroUsize::get) + })?, }) - }) + })) } } @@ -136,88 +120,6 @@ impl PartialEq for InnerAttrs { && *self.transformed == *other.transformed && *self.context == *other.context && *self.error == *other.error - && self.number_of_events == other.number_of_events - } -} - -/// TODO -#[derive(Debug, Default)] -pub struct EventAttrs { - event: Option, - number_of_events: Option, -} - -#[allow(dead_code)] -struct EventAttrsParse { - event: syn::Type, - comma: Option, - ident: Option, - equal: Option, - number: Option, -} - -impl Parse for EventAttrs { - fn parse(input: ParseStream<'_>) -> syn::Result { - input - .peek(token::Paren) - .then(|| { - let content; - syn::parenthesized!(content in input); - Ok(content) - }) - .transpose()? - .map_or_else( - || { - Ok(EventAttrs { - event: Some(input.parse::()?), - number_of_events: None, - }) - }, - |input| { - let parsed = EventAttrsParse { - event: input.parse()?, - comma: input.parse()?, - ident: input.parse()?, - equal: input.parse()?, - number: input.parse()?, - }; - - if let Some(ident) = &parsed.ident { - if ident != "number_of_events" { - return Err(syn::Error::new( - parsed.ident.span(), - "expected number_of_events", - )); - } - } - - Ok(EventAttrs { - event: Some(parsed.event), - number_of_events: parsed.number, - }) - }, - ) - } -} - -impl ToTokens for EventAttrs { - fn to_tokens(&self, tokens: &mut TokenStream) { - let event = &self.event; - let attr = if let Some(num) = &self.number_of_events { - quote! { (#event, number_of_events = #num) } - } else { - quote! { #event } - }; - - attr.to_tokens(tokens); - } -} - -// TODO: add PartialEq impls in synthez -impl PartialEq for EventAttrs { - fn eq(&self, other: &Self) -> bool { - self.event == other.event - && self.number_of_events == other.number_of_events } } @@ -265,7 +167,7 @@ pub struct ImplDefinition { pub error: syn::Type, /// TODO - pub number_of_events: NonZeroUsize, + pub max_number_of_variants: usize, } impl TryFrom for Definition { @@ -296,12 +198,29 @@ impl TryFrom for Definition { } impl Definition { + /// TODO + pub const MAX_NUMBER_OF_VARIANTS: usize = 256; + /// Generates code to derive [`Transformer`][0] trait. /// /// [0]: arcana_core::es::adapter::Transformer #[must_use] pub fn derive_transformer(&self) -> TokenStream { let adapter = &self.adapter; + let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); + let codegen_path = quote! { ::arcana::es::adapter::codegen }; + let specialization_path = quote! { + ::arcana::es::adapter::transformer::specialization + }; + let assert_fn = Self::assert_impl_any( + &syn::Ident::new("event", Span::call_site()), + [ + parse_quote! { ::arcana::es::event::Versioned }, + parse_quote! { + ::arcana::es::adapter::TransformedBy<#adapter> + }, + ], + ); self.transformers.iter().map(|tr| { let ImplDefinition { @@ -309,23 +228,41 @@ impl Definition { transformed, context, error, - number_of_events, + max_number_of_variants, } = tr; - let inner_match = self.inner_match(transformed, *number_of_events); - let (impl_gen, type_gen, where_clause) = - self.generics.split_for_impl(); - - let number_of_events = number_of_events.get(); - let codegen_path = quote! { ::arcana::es::adapter::codegen }; - let specialization_path = quote! { - arcana::es::adapter::transformer::specialization - }; + let max = *max_number_of_variants; + let id = 0..max; + let gets = quote! { + #( if ::std::option::Option::is_some( + &#specialization_path::Get::<{ + #id % <#event as #specialization_path::EnumSize>::SIZE + }>::get(&event) + ) { + let event = #specialization_path::Get::<{ + #id % <#event as #specialization_path::EnumSize>::SIZE + }>::unwrap(event); + let check = #assert_fn; + let event = check(); + + return ::std::boxed::Box::pin( + (&&&&&Wrap::<&#adapter, _, #transformed>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), + ); + } else )* + { + unreachable!() + } + }; quote! { - ::arcana::es::adapter::transformer::wrong_number_of_events!( - #number_of_events == - <#event as #specialization_path::UnpackEnum>::TUPLE_SIZE, + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + <#event as #specialization_path::EnumSize>::SIZE < #max ); #[automatically_derived] @@ -349,9 +286,10 @@ impl Definition { >; #[allow( + clippy::modulo_one, + clippy::needless_borrow, clippy::too_many_lines, - clippy::unused_self, - clippy::needless_borrow + clippy::unused_self )] fn transform<'me, 'ctx>( &'me self, @@ -368,9 +306,7 @@ impl Definition { Wrap, }; - match #specialization_path::UnpackEnum::unpack(event) { - #inner_match - } + #gets } } } @@ -378,55 +314,6 @@ impl Definition { .collect() } - /// TODO - #[must_use] - pub fn inner_match( - &self, - transformed: &syn::Type, - number_of_events: NonZeroUsize, - ) -> TokenStream { - let number_of_events = number_of_events.get(); - let adapter = &self.adapter; - - let matches = (0..).take(number_of_events).map(|i| { - let before_none = iter::repeat(quote! { None }).take(i); - let after_none = - iter::repeat(quote! { None }).take(number_of_events - i - 1); - - let assert_fn = Self::assert_impl_any( - &syn::Ident::new("event", Span::call_site()), - [ - parse_quote! { ::arcana::es::event::Versioned }, - parse_quote! { - ::arcana::es::adapter::TransformedBy<#adapter> - }, - ], - ); - - quote! { - ( #( #before_none, )* Some(event), #( #after_none ),* ) => { - let check = #assert_fn; - let event = check(); - - ::std::boxed::Box::pin( - (&&&&&Wrap::<&#adapter, _, #transformed>( - self, - &event, - ::std::marker::PhantomData, - )) - .get_tag() - .transform_event(self, event, ctx), - ) - } - } - }); - - quote! { - #( #matches )* - _ => unreachable!(), - } - } - /// TODO #[must_use] pub fn assert_impl_any( diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 1a2703d..cb6b6bb 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -85,15 +85,21 @@ pub mod specialization { use futures::{future, stream, StreamExt as _}; /// TODO - pub trait UnpackEnum { + pub trait Get { /// TODO - const TUPLE_SIZE: usize; + type Out; /// TODO - type Tuple; + fn get(&self) -> Option<&Self::Out>; /// TODO - fn unpack(self) -> Self::Tuple; + fn unwrap(self) -> Self::Out; + } + + /// TODO + pub trait EnumSize { + /// TODO + const SIZE: usize; } /// TODO diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 9d43ffd..2ce9c6c 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -174,6 +174,21 @@ impl From for Initial { } } +/// TODO +pub trait TransparentFrom { + /// TODO + fn from(_: F) -> Self; +} + +impl TransparentFrom> for Initial +where + R: From, +{ + fn from(ev: Initial) -> Self { + Initial(ev.0.into()) + } +} + impl> Sourced> for Option { diff --git a/examples/adapter.rs b/examples/adapter.rs index 707321f..69f56a7 100644 --- a/examples/adapter.rs +++ b/examples/adapter.rs @@ -105,7 +105,6 @@ enum EmailAddedOrConfirmed { into = EmailAddedOrConfirmed, ctx = dyn Any, err = Infallible, - number_of_events = 3, ), )] struct EmailAdapter; diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index b8a24e0..fcaf640 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -24,6 +24,7 @@ async fn main() { .unwrap(); println!("{:?}", chat_events); + assert_eq!(chat_events.len(), 4); assert_eq!( chat, Some(domain::Chat { @@ -40,6 +41,7 @@ async fn main() { .unwrap(); println!("{:?}", email_events); + assert_eq!(email_events.len(), 2); assert_eq!( email, Some(domain::Email { @@ -56,6 +58,7 @@ async fn main() { .unwrap(); println!("{:?}", message_events); + assert_eq!(message_events.len(), 1); assert_eq!(message, Some(domain::Message)); } diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index cee8811..8a1f6fe 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -11,10 +11,10 @@ use crate::event; #[event( transformer( from( - (super::Event, number_of_events = 3), - (super::ChatEvent, number_of_events = 3), - (super::MessageEvent, number_of_events = 1), - (super::EmailEvent, number_of_events = 3), + super::Event, + super::ChatEvent, + super::MessageEvent, + super::EmailEvent, ), into = event::Chat, context = dyn Any, @@ -23,14 +23,6 @@ use crate::event; )] pub struct Adapter; -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} - impl transformer::WithStrategy for Adapter { type Strategy = strategy::Initialized>; diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 3b4a055..4ae5ae2 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -15,10 +15,10 @@ use crate::event; #[event( transformer( from( - (super::Event, number_of_events = 3), - (super::MessageEvent, number_of_events = 1), - (super::ChatEvent, number_of_events = 3), - (super::EmailEvent, number_of_events = 3), + super::Event, + super::MessageEvent, + super::ChatEvent, + super::EmailEvent, ), into = event::Email, context = dyn Any, diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 682009d..5fa39b7 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -8,10 +8,10 @@ use crate::event; #[event( transformer( from( - (super::Event, number_of_events = 3), - (super::MessageEvent, number_of_events = 1), - (super::ChatEvent, number_of_events = 3), - (super::EmailEvent, number_of_events = 3), + super::Event, + super::MessageEvent, + super::ChatEvent, + super::EmailEvent, ), into = event::Message, context = dyn Any, diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index 91a12b0..f7bac5e 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -17,4 +17,4 @@ pub use arcana_codegen::es::transformer::Transformer; #[doc(hidden)] // TODO: Replace with panic once `const_panic` is stabilized. // https://github.com/rust-lang/rust/issues/51999 -pub use arcana_codegen::sa::const_assert as wrong_number_of_events; +pub use arcana_codegen::sa::const_assert as too_many_variants_in_enum; From fc65304ad60dd5fd18798a570d979dfbee5bd199 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 13 Sep 2021 10:03:08 +0300 Subject: [PATCH 058/104] WIP [skip ci] --- Cargo.toml | 4 - codegen/impl/src/es/event/transformer.rs | 226 +++++++++++--------- core/src/es/adapter/mod.rs | 51 ++--- core/src/es/adapter/transformer/mod.rs | 141 +++++++++--- core/src/es/adapter/transformer/strategy.rs | 81 ++++--- core/src/es/event.rs | 6 + examples/adapter.rs | 134 ------------ examples/chat/src/event/chat.rs | 10 + examples/chat/src/main.rs | 23 +- examples/chat/src/storage/chat.rs | 17 +- examples/chat/src/storage/mod.rs | 20 +- examples/specialization.rs | 52 ----- src/es/event.rs | 3 +- 13 files changed, 351 insertions(+), 417 deletions(-) delete mode 100644 examples/adapter.rs delete mode 100644 examples/specialization.rs diff --git a/Cargo.toml b/Cargo.toml index 3096ddc..545d723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,10 +30,6 @@ tokio = { version = "1", features = ["full"] } pin-project = "1" static_assertions = "1" -[[example]] -name = "adapter" -required-features = ["es", "derive"] - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index 1cf75c7..1e11376 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -63,7 +63,7 @@ pub struct InnerAttrs { pub error: Required, /// TODO - #[parse(value, alias = max, validate = can_parse_as_non_zero_usize)] + #[parse(value, validate = can_parse_as_non_zero_usize)] pub max_number_of_variants: Option, } @@ -120,6 +120,7 @@ impl PartialEq for InnerAttrs { && *self.transformed == *other.transformed && *self.context == *other.context && *self.error == *other.error + && self.max_number_of_variants == other.max_number_of_variants } } @@ -197,121 +198,57 @@ impl TryFrom for Definition { } } -impl Definition { +impl ImplDefinition { /// TODO - pub const MAX_NUMBER_OF_VARIANTS: usize = 256; - - /// Generates code to derive [`Transformer`][0] trait. - /// - /// [0]: arcana_core::es::adapter::Transformer #[must_use] - pub fn derive_transformer(&self) -> TokenStream { - let adapter = &self.adapter; - let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); - let codegen_path = quote! { ::arcana::es::adapter::codegen }; + pub fn transform_event(&self, adapter: &syn::Ident) -> TokenStream { let specialization_path = quote! { ::arcana::es::adapter::transformer::specialization }; - let assert_fn = Self::assert_impl_any( + + let assert_versioned_or_transformed = Self::assert_impl_any( &syn::Ident::new("event", Span::call_site()), [ parse_quote! { ::arcana::es::event::Versioned }, - parse_quote! { - ::arcana::es::adapter::TransformedBy<#adapter> - }, + parse_quote! { ::arcana::es::adapter::TransformedBy<#adapter> }, ], ); - self.transformers.iter().map(|tr| { - let ImplDefinition { - event, - transformed, - context, - error, - max_number_of_variants, - } = tr; - - let max = *max_number_of_variants; - let id = 0..max; - let gets = quote! { - #( if ::std::option::Option::is_some( - &#specialization_path::Get::<{ - #id % <#event as #specialization_path::EnumSize>::SIZE - }>::get(&event) - ) { - let event = #specialization_path::Get::<{ - #id % <#event as #specialization_path::EnumSize>::SIZE - }>::unwrap(event); - let check = #assert_fn; - let event = check(); - - return ::std::boxed::Box::pin( - (&&&&&Wrap::<&#adapter, _, #transformed>( - self, - &event, - ::std::marker::PhantomData, - )) - .get_tag() - .transform_event(self, event, ctx), - ); - } else )* - { - unreachable!() - } - }; + let ImplDefinition { + event, + transformed, + max_number_of_variants, + .. + } = self; - quote! { - ::arcana::es::adapter::transformer::too_many_variants_in_enum!( - <#event as #specialization_path::EnumSize>::SIZE < #max + let max = *max_number_of_variants; + let id = 0..max; + quote! { + #( if ::std::option::Option::is_some( + &#specialization_path::Get::<{ + #id % <#event as #specialization_path::EnumSize>::SIZE + }>::get(&event) + ) { + let event = #specialization_path::Get::<{ + #id % <#event as #specialization_path::EnumSize>::SIZE + }>::unwrap(event); + let check = #assert_versioned_or_transformed; + let event = check(); + + return ::std::boxed::Box::pin( + (&&&&&&&Wrap::<&#adapter, _, #transformed>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), ); - - #[automatically_derived] - impl #impl_gen ::arcana::es::adapter::Transformer<#event> for - #adapter#type_gen #where_clause - { - type Context = #context; - type Error = #error; - type Transformed = #transformed; - #[allow(clippy::type_complexity)] - type TransformedStream<'me, 'ctx> = - ::std::pin::Pin< - ::std::boxed::Box< - dyn #codegen_path::futures::Stream< - Item = ::std::result::Result< - Self::Transformed, - Self::Error, - > - > - > - >; - - #[allow( - clippy::modulo_one, - clippy::needless_borrow, - clippy::too_many_lines, - clippy::unused_self - )] - fn transform<'me, 'ctx>( - &'me self, - event: #event, - ctx: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { - #[allow(unused_imports)] - use #specialization_path::{ - TransformedBySkipAdapter as _, - TransformedByAdapter as _, - TransformedByFrom as _, - TransformedByFromInitial as _, - TransformedByEmpty as _, - Wrap, - }; - - #gets - } - } + } else )* + { + unreachable!() } - }) - .collect() + } } /// TODO @@ -379,6 +316,91 @@ impl Definition { } } +impl Definition { + /// TODO + pub const MAX_NUMBER_OF_VARIANTS: usize = 50; + + /// Generates code to derive [`Transformer`][0] trait. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[must_use] + pub fn derive_transformer(&self) -> TokenStream { + let adapter = &self.adapter; + let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); + let codegen_path = quote! { ::arcana::es::adapter::codegen }; + let specialization_path = quote! { + ::arcana::es::adapter::transformer::specialization + }; + + self.transformers.iter().map(|tr| { + let transform_event = tr.transform_event(adapter); + + let ImplDefinition { + event, + transformed, + context, + error, + max_number_of_variants, + } = tr; + + let max = *max_number_of_variants; + + quote! { + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + <#event as #specialization_path::EnumSize>::SIZE <= #max + ); + + #[automatically_derived] + impl #impl_gen ::arcana::es::adapter::Transformer<#event> for + #adapter#type_gen #where_clause + { + type Context = #context; + type Error = #error; + type Transformed = #transformed; + #[allow(clippy::type_complexity)] + type TransformedStream<'out> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn #codegen_path::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + 'out + > + >; + + #[allow(clippy::modulo_one)] + fn transform<'me, 'ctx, 'out>( + &'me self, + event: #event, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + #[allow(unused_imports)] + use #specialization_path::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByFromUpcast as _, + TransformedByFromInitialUpcast as _, + TransformedByEmpty as _, + Wrap, + }; + + #transform_event + } + } + } + }) + .collect() + } +} + #[cfg(test)] mod spec { use quote::quote; diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index a40fde4..3e1018b 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -46,44 +46,51 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me, 'ctx>( + fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } impl Adapter for T where - Events: Stream, + Events: Stream + 'static, T: Transformer + 'static, T::Context: 'static, { type Context = T::Context; type Error = T::Error; type Transformed = T::Transformed; - type TransformedStream<'me, 'ctx> = TransformedStream<'me, 'ctx, T, Events>; + type TransformedStream<'out> = TransformedStream<'out, T, Events>; - fn transform_all<'me, 'ctx>( + fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { TransformedStream::new(self, events, context) } } #[pin_project] /// [`Stream`] for [`Adapter`] blanket impl. -pub struct TransformedStream<'adapter, 'ctx, Adapter, Events> +pub struct TransformedStream<'out, Adapter, Events> where Events: Stream, Adapter: Transformer, @@ -91,14 +98,12 @@ where #[pin] events: Events, #[pin] - transformed_stream: - AdapterTransformedStream<'adapter, 'ctx, Events::Item, Adapter>, - adapter: &'adapter Adapter, - context: &'ctx Adapter::Context, + transformed_stream: AdapterTransformedStream<'out, Events::Item, Adapter>, + adapter: &'out Adapter, + context: &'out Adapter::Context, } -impl<'adapter, 'ctx, Adapter, Events> Debug - for TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> Debug for TransformedStream<'out, Adapter, Events> where Events: Debug + Stream, Adapter: Debug + Transformer, @@ -113,8 +118,8 @@ where } } -type AdapterTransformedStream<'adapter, 'ctx, Event, Adapter> = future::Either< - >::TransformedStream<'adapter, 'ctx>, +type AdapterTransformedStream<'out, Event, Adapter> = future::Either< + >::TransformedStream<'out>, stream::Empty< Result< >::Transformed, @@ -123,16 +128,15 @@ type AdapterTransformedStream<'adapter, 'ctx, Event, Adapter> = future::Either< >, >; -impl<'adapter, 'ctx, Adapter, Events> - TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> TransformedStream<'out, Adapter, Events> where Events: Stream, Adapter: Transformer, { fn new( - adapter: &'adapter Adapter, + adapter: &'out Adapter, events: Events, - context: &'ctx Adapter::Context, + context: &'out Adapter::Context, ) -> Self { Self { events, @@ -143,8 +147,7 @@ where } } -impl<'adapter, 'ctx, Adapter, Events> Stream - for TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> Stream for TransformedStream<'out, Adapter, Events> where Events: Stream, Adapter: Transformer, diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index cb6b6bb..d358810 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -39,19 +39,22 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( &'me self, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } /// Instead of implementing [`Transformer`] manually, you can use this trait @@ -80,7 +83,7 @@ pub mod specialization { transformer::{strategy, WithStrategy}, Transformer, }, - event, + event::{self, Upcast}, }; use futures::{future, stream, StreamExt as _}; @@ -122,7 +125,7 @@ pub mod specialization { } impl TransformedBySkipAdapter - for &&&&Wrap<&Adapter, &Event, TransformedEvent> + for &&&&&&Wrap<&Adapter, &Event, TransformedEvent> where Adapter: WithStrategy, { @@ -159,7 +162,7 @@ pub mod specialization { } impl TransformedByAdapter - for &&&Wrap<&Adapter, &Event, TransformedEvent> + for &&&&&Wrap<&Adapter, &Event, TransformedEvent> where Adapter: Transformer, { @@ -174,34 +177,35 @@ pub mod specialization { impl AdapterTag { /// TODO - pub fn transform_event<'me, 'ctx, Adapter, Event, TrEvent, Ctx, Err>( + pub fn transform_event<'me, 'ctx, 'out, Adapter, Ev, TrEv, Ctx, Err>( self, adapter: &'me Adapter, - ev: Event, + ev: Ev, context: &'ctx Ctx, - ) -> AdapterTagStream<'me, 'ctx, Adapter, Event, TrEvent, Err> + ) -> AdapterTagStream<'out, Adapter, Ev, TrEv, Err> where - Event: 'static, + 'me: 'out, + 'ctx: 'out, + Ev: 'static, Ctx: ?Sized, - Adapter: Transformer, - TrEvent: From, + Adapter: Transformer, + TrEv: From, Err: From, { - >::transform(adapter, ev, context) + >::transform(adapter, ev, context) .map(|res| res.map(Into::into).map_err(Into::into)) } } - type AdapterTagStream<'me, 'ctx, Adapter, Event, TrEvent, Err> = - stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - Result< - >::Transformed, - >::Error, - >, - ) -> Result, - >; + type AdapterTagStream<'out, Adapter, Event, TrEvent, Err> = stream::Map< + >::TransformedStream<'out>, + fn( + Result< + >::Transformed, + >::Error, + >, + ) -> Result, + >; // With From @@ -212,7 +216,7 @@ pub mod specialization { } impl TransformedByFrom - for &&Wrap<&Adapter, &Event, TransformedEvent> + for &&&&Wrap<&Adapter, &Event, TransformedEvent> where TransformedEvent: From, { @@ -250,7 +254,7 @@ pub mod specialization { } impl TransformedByFromInitial - for &Wrap<&Adapter, &Event, TransformedEvent> + for &&&Wrap<&Adapter, &Event, TransformedEvent> where TransformedEvent: From>, { @@ -279,6 +283,89 @@ pub mod specialization { } } + // With From Upcast + + /// TODO + pub trait TransformedByFromUpcast { + /// TODO + fn get_tag(&self) -> FromUpcastTag; + } + + impl TransformedByFromUpcast + for &Wrap<&Adapter, &Event, TransformedEvent> + where + Event: Upcast, + TransformedEvent: From, + { + fn get_tag(&self) -> FromUpcastTag { + FromUpcastTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct FromUpcastTag; + + impl FromUpcastTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + ev: Event, + _: &Ctx, + ) -> stream::Once>> + where + Ctx: ?Sized, + Event: Upcast, + TrEvent: From, + { + stream::once(future::ready(Ok(Event::Into::from(ev).into()))) + } + } + + // With From Initial Upcast + + /// TODO + pub trait TransformedByFromInitialUpcast { + /// TODO + fn get_tag(&self) -> FromInitialUpcastTag; + } + + impl TransformedByFromInitialUpcast + for &Wrap<&Adapter, &Event, TransformedEvent> + where + Event: Upcast, + TransformedEvent: From>, + { + fn get_tag(&self) -> FromInitialUpcastTag { + FromInitialUpcastTag + } + } + + /// TODO + #[derive(Clone, Copy, Debug)] + pub struct FromInitialUpcastTag; + + impl FromInitialUpcastTag { + /// TODO + pub fn transform_event( + self, + _: &Adapter, + ev: Event, + _: &Ctx, + ) -> stream::Once>> + where + Ctx: ?Sized, + Event: Upcast, + TrEvent: From>, + { + stream::once(future::ready(Ok(event::Initial(Event::Into::from( + ev, + )) + .into()))) + } + } + // Skip /// TODO diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index a7040aa..429751b 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -33,19 +33,22 @@ pub trait Strategy { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } impl Transformer for Adapter @@ -57,16 +60,20 @@ where type Error = >::Error; type Transformed = >::Transformed; - type TransformedStream<'me, 'ctx> = = >::TransformedStream<'me, 'ctx>; + >>::TransformedStream<'out>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( &'me self, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { >::transform( self, event, context, ) @@ -84,20 +91,25 @@ impl Strategy for Initialized where InnerStrategy: Strategy, + InnerStrategy::Transformed: 'static, { type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = event::Initial; - type TransformedStream<'me, 'ctx> = stream::MapOk< - InnerStrategy::TransformedStream<'me, 'ctx>, + type TransformedStream<'out> = stream::MapOk< + InnerStrategy::TransformedStream<'out>, WrapInitial, >; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) } } @@ -127,14 +139,17 @@ impl Strategy for Skip { type Context = dyn Any; type Error = Infallible; type Transformed = Unknown; - type TransformedStream<'me, 'ctx> = - stream::Empty>; + type TransformedStream<'out> = stream::Empty>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( _: &'me Adapter, _: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::empty() } } @@ -149,14 +164,18 @@ impl Strategy for AsIs { type Context = dyn Any; type Error = Infallible; type Transformed = Event; - type TransformedStream<'me, 'ctx> = + type TransformedStream<'out> = stream::Once>>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( _: &'me Adapter, event: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::once(future::ready(Ok(event))) } } @@ -187,14 +206,18 @@ where type Context = dyn Any; type Error = Infallible; type Transformed = IntoEvent; - type TransformedStream<'me, 'ctx> = + type TransformedStream<'out> = stream::Once>>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( _: &'me Adapter, event: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::once(future::ready(Ok(IntoEvent::from(event)))) } } @@ -236,18 +259,24 @@ impl Debug for Split { impl Strategy for Split where + IntoEvent: 'static, Adapter: Splitter, + Adapter::Iterator: 'static, { type Context = dyn Any; type Error = Infallible; type Transformed = ::Item; - type TransformedStream<'me, 'ctx> = SplitStream; + type TransformedStream<'out> = SplitStream; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::iter(adapter.split(event)).map(Ok) } } diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 2ce9c6c..a0b23ac 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -197,6 +197,12 @@ impl> Sourced> } } +/// TODO +pub trait Upcast: Sized { + /// TODO + type Into: From; +} + #[cfg(feature = "codegen")] pub mod codegen { //! [`Event`] machinery aiding codegen. diff --git a/examples/adapter.rs b/examples/adapter.rs deleted file mode 100644 index 69f56a7..0000000 --- a/examples/adapter.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![feature(generic_associated_types)] -#![feature(more_qualified_paths)] - -use std::{any::Any, array, convert::Infallible}; - -use arcana::es::{ - adapter::{ - transformer::{self, strategy}, - Adapter, Transformer, - }, - event, Event, -}; -use derive_more::From; -use futures::{stream, TryStreamExt as _}; - -#[tokio::main] -async fn main() { - let ctx = 1_usize; // Can be any type in this example. - let events = stream::iter::<[InputEmailEvents; 5]>([ - InnerInputEvents::from(EmailConfirmed { - confirmed_by: "1".to_string(), - }) - .into(), - InnerInputEvents::from(EmailAdded { - email: "2".to_string(), - }) - .into(), - InnerInputEvents::from(EmailAddedAndConfirmed { - email: "3".to_string(), - confirmed_by: "3".to_string(), - }) - .into(), - SkippedEvent.into(), - Custom.into(), - ]); - - let collect = EmailAdapter - .transform_all(events, &ctx) - .try_collect::>() - .await - .unwrap(); - - println!("context: {}\nevents: {:?}", ctx, collect); -} - -// Individual events - -#[derive(Debug, event::Versioned)] -#[event(name = "custom", version = 1)] -struct Custom; - -#[derive(Debug, event::Versioned)] -#[event(name = "skipped", version = 1)] -struct SkippedEvent; - -#[derive(Debug, event::Versioned)] -#[event(name = "email.added_and_confirmed", version = 1)] -struct EmailAddedAndConfirmed { - email: String, - confirmed_by: String, -} - -#[derive(Debug, event::Versioned)] -#[event(name = "email.added", version = 1)] -struct EmailAdded { - email: String, -} - -#[derive(Debug, event::Versioned)] -#[event(name = "email.confirmed", version = 1)] -struct EmailConfirmed { - confirmed_by: String, -} - -// Input events enum - -#[derive(Debug, Event, From)] -enum InputEmailEvents { - Custom(Custom), - Skipped(SkippedEvent), - Inner(InnerInputEvents), -} - -#[derive(Debug, Event, From)] -enum InnerInputEvents { - AddedAndConfirmed(EmailAddedAndConfirmed), - Added(EmailAdded), - Confirmed(EmailConfirmed), -} - -// Output events enum - -#[derive(Debug, Event, From)] -enum EmailAddedOrConfirmed { - Added(EmailAdded), - Confirmed(event::Initial), -} - -// Adapter - -#[derive(Transformer)] -#[event( - transformer( - from(InputEmailEvents, InnerInputEvents), - into = EmailAddedOrConfirmed, - ctx = dyn Any, - err = Infallible, - ), -)] -struct EmailAdapter; - -impl transformer::WithStrategy for EmailAdapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for EmailAdapter { - type Strategy = strategy::Split; -} - -impl strategy::Splitter - for EmailAdapter -{ - type Iterator = array::IntoIter; - - fn split(&self, ev: EmailAddedAndConfirmed) -> Self::Iterator { - array::IntoIter::new([ - EmailAdded { email: ev.email }.into(), - event::Initial(EmailConfirmed { - confirmed_by: ev.confirmed_by, - }) - .into(), - ]) - } -} diff --git a/examples/chat/src/event/chat.rs b/examples/chat/src/event/chat.rs index cc6cc94..bdc8695 100644 --- a/examples/chat/src/event/chat.rs +++ b/examples/chat/src/event/chat.rs @@ -22,4 +22,14 @@ pub mod v1 { #[derive(Debug, event::Versioned)] #[event(name = "chat.created", version = 1)] pub struct Created; + + impl From for super::private::Created { + fn from(_: Created) -> Self { + Self + } + } + + impl event::Upcast for Created { + type Into = super::private::Created; + } } diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index fcaf640..baa9870 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -9,7 +9,6 @@ use std::array; use arcana::es::{event::Sourced, EventAdapter as _}; use futures::{stream, Stream, TryStreamExt as _}; -#[allow(clippy::semicolon_if_nothing_returned)] #[tokio::main] async fn main() { let mut chat = Option::::None; @@ -33,6 +32,17 @@ async fn main() { }), ); + let message_events = storage::message::Adapter + .transform_all(incoming_events(), &()) + .inspect_ok(|ev| message.apply(ev)) + .try_collect::>() + .await + .unwrap(); + println!("{:?}", message_events); + + assert_eq!(message_events.len(), 1); + assert_eq!(message, Some(domain::Message)); + let email_events = storage::email::Adapter .transform_all(incoming_events(), &()) .inspect_ok(|ev| email.apply(ev)) @@ -49,17 +59,6 @@ async fn main() { confirmed_by: Some("Test".to_owned()), }) ); - - let message_events = storage::message::Adapter - .transform_all(incoming_events(), &()) - .inspect_ok(|ev| message.apply(ev)) - .try_collect::>() - .await - .unwrap(); - println!("{:?}", message_events); - - assert_eq!(message_events.len(), 1); - assert_eq!(message, Some(domain::Message)); } fn incoming_events() -> impl Stream { diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 8a1f6fe..b6d7132 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,9 +1,6 @@ use std::{any::Any, convert::Infallible}; -use arcana::es::adapter::{ - transformer::{self, strategy}, - Transformer, -}; +use arcana::es::adapter::Transformer; use crate::event; @@ -22,15 +19,3 @@ use crate::event; ), )] pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = - strategy::Initialized>; -} - -// Chats are private by default. -impl From for event::chat::private::Created { - fn from(_: event::chat::v1::Created) -> Self { - Self - } -} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 2ed56ab..6752beb 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -2,7 +2,7 @@ pub mod chat; pub mod email; pub mod message; -use arcana::es::{self, adapter::transformer::strategy}; +use arcana::es; use derive_more::From; use crate::event; @@ -32,21 +32,3 @@ pub enum EmailEvent { Confirmed(event::email::Confirmed), AddedAndConfirmed(event::email::v1::AddedAndConfirmed), } - -impl From for event::Chat { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -impl From for event::Email { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -impl From for event::Message { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} diff --git a/examples/specialization.rs b/examples/specialization.rs deleted file mode 100644 index c773177..0000000 --- a/examples/specialization.rs +++ /dev/null @@ -1,52 +0,0 @@ -use futures::{ - future, - stream::{self, LocalBoxStream, StreamExt}, -}; - -struct Event; - -trait TransformedByDefault { - fn transform(self, adapter: Adapter) -> LocalBoxStream<'static, Event>; -} - -impl TransformedByDefault for &Ev { - fn transform(self, _: Adapter) -> LocalBoxStream<'static, Event> { - stream::empty().boxed_local() - } -} - -trait TransformedBy { - fn transform(self, adapter: Adapter) -> LocalBoxStream<'static, Event>; -} - -struct A; - -struct Skipped; - -struct Once; - -impl TransformedBy for Once { - fn transform(self, _: A) -> LocalBoxStream<'static, Event> { - stream::once(future::ready(Event)).boxed_local() - } -} - -#[tokio::main] -async fn main() { - trait Test {} - - impl Test for A {} - - let once = Once; - let skipped = Skipped; - - assert_eq!(once.transform(A).collect::>().await.len(), 1); - assert_eq!(skipped.transform(A).collect::>().await.len(), 0); -} - -trait Foo {} -trait MarkerFoo {} - -impl Foo for T {} - -impl Foo for A {} diff --git a/src/es/event.rs b/src/es/event.rs index bac97e7..5d46d28 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -2,7 +2,8 @@ #[doc(inline)] pub use arcana_core::es::event::{ - Event, Initial, Initialized, Name, Sourced, Sourcing, Version, Versioned, + Event, Initial, Initialized, Name, Sourced, Sourcing, Upcast, Version, + Versioned, }; #[cfg(feature = "derive")] From 8416aaa0e2c7f918194cd223bd586e90c6cbf6f3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 13 Sep 2021 14:59:15 +0300 Subject: [PATCH 059/104] WIP [skip ci] --- codegen/impl/src/es/event/mod.rs | 195 +++- codegen/impl/src/es/event/transformer.rs | 1083 ++++++++++++++----- codegen/impl/src/es/event/versioned.rs | 3 - codegen/shim/src/lib.rs | 118 +- codegen/src/lib.rs | 2 - core/src/es/adapter/mod.rs | 9 - core/src/es/adapter/transformer/mod.rs | 90 +- core/src/es/adapter/transformer/strategy.rs | 44 +- core/src/es/event.rs | 20 + examples/chat/src/main.rs | 1 + src/es/adapter/mod.rs | 4 - 11 files changed, 1109 insertions(+), 460 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 16f3c4a..f09d036 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -39,7 +39,7 @@ pub struct VariantAttrs { #[to_tokens(append( impl_event, impl_event_sourced, - get_impl, + impl_get_and_enum_size, gen_uniqueness_glue_code ))] pub struct Definition { @@ -235,13 +235,18 @@ impl Definition { } } - /// TODO + /// Generates hidden machinery code used to transform [`Event`] value to + /// it's corresponding variant be iterating over [`Get`] trait. + /// Used in [`Transformer`] derive macro expansion. + /// + /// [`Event`]: arcana_core::es::event::Event + /// [`Get`]: arcana_core::es::event::codegen::Get + /// [`Transformer`]: arcana_core::es::adapter::Transformer #[must_use] - pub fn get_impl(&self) -> TokenStream { + pub fn impl_get_and_enum_size(&self) -> TokenStream { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let spec_path = - quote! { ::arcana::es::adapter::transformer::specialization }; + let codegen_path = quote! { ::arcana::es::event::codegen }; let (id, (var_ident, var_ty)): (Vec<_>, (Vec<_>, Vec<_>)) = self .variants @@ -253,30 +258,36 @@ impl Definition { let number_of_variants = id.len(); quote! { - impl#impl_gens #spec_path::EnumSize for #ty#ty_gens - #where_clause + #[automatically_derived] + #[doc(hidden)] + impl#impl_gens #codegen_path::EnumSize for #ty#ty_gens #where_clause { const SIZE: usize = #number_of_variants; } - #( impl#impl_gens #spec_path::Get<#id> for #ty#ty_gens #where_clause - { - type Out = #var_ty; - - fn get(&self) -> ::std::option::Option<&Self::Out> { - if let Self::#var_ident(v) = self { - return Some(v); + #( + #[automatically_derived] + #[doc(hidden)] + impl#impl_gens #codegen_path::Get<#id> for #ty#ty_gens + #where_clause + { + type Out = #var_ty; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::#var_ident(v) = self { + return Some(v); + } + None } - None - } - fn unwrap(self) -> Self::Out { - if let Self::#var_ident(v) = self { - return v; + fn unwrap(self) -> Self::Out { + if let Self::#var_ident(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") } - panic!("called `Option::unwrap()` on a `None` value") } - } )* + )* } } @@ -284,15 +295,8 @@ impl Definition { /// [`Event::name`][0]s and [`Event::version`][1]s pairs are corresponding /// to a single Rust type. /// - /// # Panics - /// - /// If some enum [`Variant`]s don't have exactly 1 [`Field`] and not marked - /// with `#[event(skip)]`. Checked by [`TryFrom`] impl for [`Definition`]. - /// /// [0]: arcana_core::es::event::Event::name() /// [1]: arcana_core::es::event::Event::version() - /// [`Field`]: syn::Field - /// [`Variant`]: syn::Variant #[must_use] pub fn gen_uniqueness_glue_code(&self) -> TokenStream { let ty = &self.ident; @@ -420,6 +424,49 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::es::event::codegen::EnumSize for Event { + const SIZE: usize = 2usize; + } + + #[automatically_derived] + impl ::arcana::es::event::codegen::Get<0usize> for Event { + type Out = FileEvent; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::File(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::File(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + + #[automatically_derived] + impl ::arcana::es::event::codegen::Get<1usize> for Event { + type Out = ChatEvent; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::Chat(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::Chat(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { @@ -544,6 +591,55 @@ mod spec { } } + #[automatically_derived] + impl<'a, F, C> ::arcana::es::event::codegen::EnumSize for + Event<'a, F, C> + { + const SIZE: usize = 2usize; + } + + #[automatically_derived] + impl<'a, F, C> ::arcana::es::event::codegen::Get<0usize> for + Event<'a, F, C> + { + type Out = FileEvent<'a, F>; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::File(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::File(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + + #[automatically_derived] + impl<'a, F, C> ::arcana::es::event::codegen::Get<1usize> for + Event<'a, F, C> + { + type Out = ChatEvent<'a, C>; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::Chat(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::Chat(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + #[automatically_derived] #[doc(hidden)] impl<'a, F, C> ::arcana::es::event::codegen::Versioned @@ -682,6 +778,49 @@ mod spec { } } + #[automatically_derived] + impl ::arcana::es::event::codegen::EnumSize for Event { + const SIZE: usize = 2usize; + } + + #[automatically_derived] + impl ::arcana::es::event::codegen::Get<0usize> for Event { + type Out = FileEvent; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::File(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::File(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + + #[automatically_derived] + impl ::arcana::es::event::codegen::Get<1usize> for Event { + type Out = ChatEvent; + + fn get(&self) -> ::std::option::Option<&Self::Out> { + if let Self::Chat(v) = self { + return Some(v); + } + None + } + + fn unwrap(self) -> Self::Out { + if let Self::Chat(v) = self { + return v; + } + panic!("called `Option::unwrap()` on a `None` value") + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index 1e11376..a5132e8 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -13,9 +13,7 @@ use synthez::{ParseAttrs, Required, Spanning, ToTokens}; /// /// # Errors /// -/// - If `input` isn't a Rust enum definition; -/// - If some enum variant is not a single-field tuple struct; -/// - If failed to parse [`Attrs`]. +/// If failed to parse [`Attrs`] or transform them into [`Definition`]. pub fn derive(input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; let definition = Definition::try_from(input)?; @@ -40,9 +38,11 @@ pub struct Attrs { /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug, Default, ParseAttrs)] pub struct InnerAttrs { - /// TODO - #[parse(value, alias = from)] - pub event: Vec, + /// [`Transformer`][0] generic types. + /// + /// [0]: arcana_core::es::adapter::Transformer + #[parse(value, alias(from, event))] + pub events: Vec, /// [`Transformer::Transformed`][0] type. /// @@ -62,7 +62,9 @@ pub struct InnerAttrs { #[parse(value, alias = err)] pub error: Required, - /// TODO + /// Maximum number of allowed [`Self::events`] enum variants. + // TODO: remove this restrictions, once we will be able to constantly + // traverse const array #[parse(value, validate = can_parse_as_non_zero_usize)] pub max_number_of_variants: Option, } @@ -77,12 +79,22 @@ fn can_parse_as_non_zero_usize<'a>( .map(drop) } +/// Default value for [`ImplDefinition::max_number_of_variants`]. +pub const MAX_NUMBER_OF_VARIANTS: usize = 50; + impl InnerAttrs { - fn into_impl_definition( + /// Transforms [`InnerAttrs`] into [`ImplDefinition`]s. + /// + /// # Errors + /// + /// - If [`InnerAttrs::events`] is empty; + /// - If failed to parse [`Self::max_number_of_variants`] into + /// [`NonZeroUsize`]. + pub fn into_impl_definition( self, ) -> impl Iterator> { let InnerAttrs { - event, + events: event, transformed, context, error, @@ -104,7 +116,7 @@ impl InnerAttrs { error: error.deref().clone(), max_number_of_variants: max_number_of_variants .as_ref() - .map_or(Ok(Definition::MAX_NUMBER_OF_VARIANTS), |max| { + .map_or(Ok(MAX_NUMBER_OF_VARIANTS), |max| { max.base10_parse::() .map(NonZeroUsize::get) })?, @@ -116,7 +128,7 @@ impl InnerAttrs { // TODO: add PartialEq impls in synthez impl PartialEq for InnerAttrs { fn eq(&self, other: &Self) -> bool { - *self.event == *other.event + *self.events == *other.events && *self.transformed == *other.transformed && *self.context == *other.context && *self.error == *other.error @@ -124,14 +136,17 @@ impl PartialEq for InnerAttrs { } } -/// Representation of a enum for implementing [`Transformer`][0], used for code +/// Representation of a type for implementing [`Transformer`][0], used for code /// generation. /// /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug, ToTokens)] -#[to_tokens(append(derive_transformer))] +#[to_tokens(append(derive_transformers))] pub struct Definition { - /// TODO + /// [`syn::Ident`](struct@syn::Ident) of type [`Transformer`][0] is derived + /// on. + /// + /// [0]: arcana_core::es::adapter::Transformer pub adapter: syn::Ident, /// [`syn::Generics`] of this enum's type. @@ -143,13 +158,14 @@ pub struct Definition { pub transformers: Vec, } -/// Representation of a struct implementing [`Transformer`][0], used for code -/// generation. +/// Representation of [`Transformer`][0] impl, used for code generation. /// /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug)] pub struct ImplDefinition { - /// TODO + /// [`Transformer`][0] generic type. + /// + /// [0]: arcana_core::es::adapter::Transformer pub event: syn::Type, /// [`Transformer::Transformed`][0] type. @@ -167,7 +183,7 @@ pub struct ImplDefinition { /// [0]: arcana_core::es::adapter::Transformer::Error pub error: syn::Type, - /// TODO + /// Maximum number of allowed [`Self::event`] enum variants. pub max_number_of_variants: usize, } @@ -198,18 +214,111 @@ impl TryFrom for Definition { } } -impl ImplDefinition { - /// TODO +impl Definition { + /// Generates code to derive [`Transformer`][0] traits. + /// + /// [0]: arcana_core::es::adapter::Transformer #[must_use] - pub fn transform_event(&self, adapter: &syn::Ident) -> TokenStream { + pub fn derive_transformers(&self) -> TokenStream { + let adapter = &self.adapter; + let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); + let codegen_path = quote! { ::arcana::es::event::codegen }; let specialization_path = quote! { ::arcana::es::adapter::transformer::specialization }; + self.transformers.iter().map(|tr| { + let transform_event = tr.transform_event(adapter); + + let ImplDefinition { + event, + transformed, + context, + error, + max_number_of_variants, + } = tr; + + let max = *max_number_of_variants; + + quote! { + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + <#event as #codegen_path::EnumSize>::SIZE <= #max + ); + + #[automatically_derived] + impl#impl_gen ::arcana::es::adapter::Transformer<#event> for + #adapter#type_gen #where_clause + { + type Context = #context; + type Error = #error; + type Transformed = #transformed; + #[allow(clippy::type_complexity)] + type TransformedStream<'out> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn #codegen_path::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + 'out + > + >; + + #[allow(clippy::modulo_one)] + fn transform<'me, 'ctx, 'out>( + &'me self, + event: #event, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + #[allow(unused_imports)] + use #specialization_path::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByFromUpcast as _, + TransformedByFromInitialUpcast as _, + TransformedByEmpty as _, + Wrap, + }; + + #transform_event + } + } + } + }) + .collect() + } +} + +impl ImplDefinition { + /// Generates code which matches [`Event`] value with it's variant, checks + /// whether [`Versioned`] or [`TransformedBy`] is implemented for it and + /// then applies [`specialization`][0] transformations to it. + /// + /// Match is done by iterating `0..max_number_of_variants` over [`Get`] + /// trait. As before that we asserted that number of variants is less or + /// equals then `max_number_of_variants`, [`unreachable`] is really + /// unreachable. + /// + /// [0]: arcana_core::es::adapter::transformer::specialization + /// [`Event`]: arcana_core::es::Event + /// [`Get`]: arcana_core::es::event::codegen::Get + /// [`TransformedBy`]: arcana_core::es::adapter::TransformedBy + /// [`Versioned`]: arcana_core::es::event::Versioned + #[must_use] + pub fn transform_event(&self, adapter: &syn::Ident) -> TokenStream { + let codegen_path = quote! { ::arcana::es::event::codegen }; + let assert_versioned_or_transformed = Self::assert_impl_any( &syn::Ident::new("event", Span::call_site()), [ - parse_quote! { ::arcana::es::event::Versioned }, + parse_quote! { ::arcana::es::event::Versioned }, parse_quote! { ::arcana::es::adapter::TransformedBy<#adapter> }, ], ); @@ -225,17 +334,17 @@ impl ImplDefinition { let id = 0..max; quote! { #( if ::std::option::Option::is_some( - &#specialization_path::Get::<{ - #id % <#event as #specialization_path::EnumSize>::SIZE + &#codegen_path::Get::<{ + #id % <#event as #codegen_path::EnumSize>::SIZE }>::get(&event) ) { - let event = #specialization_path::Get::<{ - #id % <#event as #specialization_path::EnumSize>::SIZE + let event = #codegen_path::Get::<{ + #id % <#event as #codegen_path::EnumSize>::SIZE }>::unwrap(event); let check = #assert_versioned_or_transformed; let event = check(); - return ::std::boxed::Box::pin( + ::std::boxed::Box::pin( (&&&&&&&Wrap::<&#adapter, _, #transformed>( self, &event, @@ -243,7 +352,7 @@ impl ImplDefinition { )) .get_tag() .transform_event(self, event, ctx), - ); + ) } else )* { unreachable!() @@ -251,7 +360,12 @@ impl ImplDefinition { } } - /// TODO + /// Generates closure, which moves `value` inside, asserts whether any of + /// provided `traits` are implemented for it (if not, fails at compile time) + /// and then returns `value`. + /// + /// We can't use closure which takes `value` as parameter as type inference + /// can break assertion. #[must_use] pub fn assert_impl_any( value: &syn::Ident, @@ -316,91 +430,6 @@ impl ImplDefinition { } } -impl Definition { - /// TODO - pub const MAX_NUMBER_OF_VARIANTS: usize = 50; - - /// Generates code to derive [`Transformer`][0] trait. - /// - /// [0]: arcana_core::es::adapter::Transformer - #[must_use] - pub fn derive_transformer(&self) -> TokenStream { - let adapter = &self.adapter; - let (impl_gen, type_gen, where_clause) = self.generics.split_for_impl(); - let codegen_path = quote! { ::arcana::es::adapter::codegen }; - let specialization_path = quote! { - ::arcana::es::adapter::transformer::specialization - }; - - self.transformers.iter().map(|tr| { - let transform_event = tr.transform_event(adapter); - - let ImplDefinition { - event, - transformed, - context, - error, - max_number_of_variants, - } = tr; - - let max = *max_number_of_variants; - - quote! { - ::arcana::es::adapter::transformer::too_many_variants_in_enum!( - <#event as #specialization_path::EnumSize>::SIZE <= #max - ); - - #[automatically_derived] - impl #impl_gen ::arcana::es::adapter::Transformer<#event> for - #adapter#type_gen #where_clause - { - type Context = #context; - type Error = #error; - type Transformed = #transformed; - #[allow(clippy::type_complexity)] - type TransformedStream<'out> = - ::std::pin::Pin< - ::std::boxed::Box< - dyn #codegen_path::futures::Stream< - Item = ::std::result::Result< - Self::Transformed, - Self::Error, - > - > + 'out - > - >; - - #[allow(clippy::modulo_one)] - fn transform<'me, 'ctx, 'out>( - &'me self, - event: #event, - ctx: &'ctx Self::Context, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - #[allow(unused_imports)] - use #specialization_path::{ - TransformedBySkipAdapter as _, - TransformedByAdapter as _, - TransformedByFrom as _, - TransformedByFromInitial as _, - TransformedByFromUpcast as _, - TransformedByFromInitialUpcast as _, - TransformedByEmpty as _, - Wrap, - }; - - #transform_event - } - } - } - }) - .collect() - } -} - #[cfg(test)] mod spec { use quote::quote; @@ -408,135 +437,293 @@ mod spec { #[allow(clippy::too_many_lines)] #[test] - fn derives_enum_impl() { + fn derives_impl() { let input = parse_quote! { #[event( transformer( - adapter = Adapter, + from = Event, into = IntoEvent, context = dyn Any, error = Infallible, + max_number_of_variants = 2, ), )] - enum Event { - File(FileEvent), - Chat(ChatEvent), - } + struct Adapter; }; let output = quote! { + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + ::SIZE <= + 2usize + ); + #[automatically_derived] impl ::arcana::es::adapter::Transformer for Adapter { type Context = dyn Any; type Error = Infallible; type Transformed = IntoEvent; - type TransformedStream<'me, 'ctx> = - ::arcana::es::adapter::codegen::futures::future::Either< - ::arcana::es::adapter::codegen::futures::stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - >:: - Transformed, - >:: - Error, - >, - ) -> ::std::result::Result< - >:: - Transformed, - >::Error, - >, - >, - ::arcana::es::adapter::codegen::futures::stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - >:: - Transformed, - >:: - Error, - >, - ) -> ::std::result::Result< - >:: - Transformed, - >::Error, - >, - >, + #[allow(clippy::type_complexity)] + type TransformedStream<'out> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn ::arcana::es::event::codegen::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + 'out + > >; - fn transform<'me, 'ctx>( + #[allow(clippy::modulo_one)] + fn transform<'me, 'ctx, 'out>( &'me self, - __event: Event, - __context: - &'ctx >::Context, - ) -> >:: - TransformedStream<'me, 'ctx> + event: Event, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, { - match __event { - Event::File(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: - left_stream( - ::arcana::es::adapter::codegen::futures:: - StreamExt::map( - - >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = - |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) + #[allow(unused_imports)] + use ::arcana::es::adapter::transformer::specialization::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByFromUpcast as _, + TransformedByFromInitialUpcast as _, + TransformedByEmpty as _, + Wrap, + }; + + if ::std::option::Option::is_some( + &::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::get(&event) + ) { + let event = + ::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::unwrap(event); + let check = || { + struct AssertImplAnyFallback; + + struct ActualAssertImplAnyToken; + trait AssertImplAnyToken {} + impl AssertImplAnyToken for + ActualAssertImplAnyToken {} + + fn assert_impl_any_token(_: T) + where T: AssertImplAnyToken {} + + let previous = AssertImplAnyFallback; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::event::Versioned, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::adapter::TransformedBy< + Adapter + >, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + assert_impl_any_token( + previous._static_assertions_impl_any(), + ); + + event + }; + let event = check(); + + ::std::boxed::Box::pin( + (&&&&&&&Wrap::<&Adapter, _, IntoEvent>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), ) - }, - Event::Chat(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: - right_stream( - ::arcana::es::adapter::codegen::futures:: - StreamExt::map( - - >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = - |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) + } else if ::std::option::Option::is_some( + &::arcana::es::event::codegen::Get::<{ + 1usize % ::SIZE + }>::get(&event) + ) { + let event = + ::arcana::es::event::codegen::Get::<{ + 1usize % ::SIZE + }>::unwrap(event); + let check = || { + struct AssertImplAnyFallback; + + struct ActualAssertImplAnyToken; + trait AssertImplAnyToken {} + impl AssertImplAnyToken for + ActualAssertImplAnyToken {} + + fn assert_impl_any_token(_: T) + where T: AssertImplAnyToken {} + + let previous = AssertImplAnyFallback; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::event::Versioned, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::adapter::TransformedBy< + Adapter + >, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + assert_impl_any_token( + previous._static_assertions_impl_any(), + ); + + event + }; + let event = check(); + + ::std::boxed::Box::pin( + (&&&&&&&Wrap::<&Adapter, _, IntoEvent>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), ) - }, + } else { + unreachable!() + } } } - } }; assert_eq!( @@ -545,91 +732,404 @@ mod spec { ); } + #[allow(clippy::too_many_lines)] #[test] - fn errors_on_without_adapter_attribute() { - let input = parse_quote! { + fn derives_multiple_impls() { + let shorter_input = parse_quote! { #[event( transformer( - transformed = IntoEvent, + events(FirstEvent, SecondEvent), + into = IntoEvent, context = dyn Any, error = Infallible, + max_number_of_variants = 1, ), )] - enum Event { - Event1(Event1), - Event2(Event2), - } + struct Adapter; }; - let err = super::derive(input).unwrap_err(); - - assert_eq!( - err.to_string(), - "`adapter` argument of `#[event(transformer)]` attribute is \ - expected to be present, but is absent", - ); - } - - #[test] - fn errors_on_without_transformed_attribute() { - let input = parse_quote! { + let longer_input = parse_quote! { #[event( transformer( - adapter = Adapter, + event = FirstEvent, + into = IntoEvent, context = dyn Any, error = Infallible, + max_number_of_variants = 1, + ), + transformer( + event = SecondEvent, + into = IntoEvent, + context = dyn Any, + error = Infallible, + max_number_of_variants = 1, ), )] - enum Event { - Event1(Event1), - Event2(Event2), + struct Adapter; + }; + + let output = quote! { + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + ::SIZE <= + 1usize + ); + + #[automatically_derived] + impl ::arcana::es::adapter::Transformer for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = IntoEvent; + #[allow(clippy::type_complexity)] + type TransformedStream<'out> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn ::arcana::es::event::codegen::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + 'out + > + >; + + #[allow(clippy::modulo_one)] + fn transform<'me, 'ctx, 'out>( + &'me self, + event: FirstEvent, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + #[allow(unused_imports)] + use ::arcana::es::adapter::transformer::specialization::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByFromUpcast as _, + TransformedByFromInitialUpcast as _, + TransformedByEmpty as _, + Wrap, + }; + + if ::std::option::Option::is_some( + &::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::get(&event) + ) { + let event = + ::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::unwrap(event); + let check = || { + struct AssertImplAnyFallback; + + struct ActualAssertImplAnyToken; + trait AssertImplAnyToken {} + impl AssertImplAnyToken for + ActualAssertImplAnyToken {} + + fn assert_impl_any_token(_: T) + where T: AssertImplAnyToken {} + + let previous = AssertImplAnyFallback; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::event::Versioned, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::adapter::TransformedBy< + Adapter + >, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + assert_impl_any_token( + previous._static_assertions_impl_any(), + ); + + event + }; + let event = check(); + + ::std::boxed::Box::pin( + (&&&&&&&Wrap::<&Adapter, _, IntoEvent>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), + ) + } else { + unreachable!() + } + } + } + + ::arcana::es::adapter::transformer::too_many_variants_in_enum!( + ::SIZE <= + 1usize + ); + + #[automatically_derived] + impl ::arcana::es::adapter::Transformer for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = IntoEvent; + #[allow(clippy::type_complexity)] + type TransformedStream<'out> = + ::std::pin::Pin< + ::std::boxed::Box< + dyn ::arcana::es::event::codegen::futures::Stream< + Item = ::std::result::Result< + Self::Transformed, + Self::Error, + > + > + 'out + > + >; + + #[allow(clippy::modulo_one)] + fn transform<'me, 'ctx, 'out>( + &'me self, + event: SecondEvent, + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + #[allow(unused_imports)] + use ::arcana::es::adapter::transformer::specialization::{ + TransformedBySkipAdapter as _, + TransformedByAdapter as _, + TransformedByFrom as _, + TransformedByFromInitial as _, + TransformedByFromUpcast as _, + TransformedByFromInitialUpcast as _, + TransformedByEmpty as _, + Wrap, + }; + + if ::std::option::Option::is_some( + &::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::get(&event) + ) { + let event = + ::arcana::es::event::codegen::Get::<{ + 0usize % ::SIZE + }>::unwrap(event); + let check = || { + struct AssertImplAnyFallback; + + struct ActualAssertImplAnyToken; + trait AssertImplAnyToken {} + impl AssertImplAnyToken for + ActualAssertImplAnyToken {} + + fn assert_impl_any_token(_: T) + where T: AssertImplAnyToken {} + + let previous = AssertImplAnyFallback; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::event::Versioned, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + let previous = { + struct Wrapper( + ::std::marker::PhantomData, + N, + ); + + impl ::std::ops::Deref for Wrapper { + type Target = N; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + + impl Wrapper { + fn new(_: &T, right: N) -> Wrapper { + Self(::std::marker::PhantomData, right) + } + } + + impl Wrapper + where + T: ::arcana::es::adapter::TransformedBy< + Adapter + >, + { + fn _static_assertions_impl_any( + &self, + ) -> ActualAssertImplAnyToken { + ActualAssertImplAnyToken + } + } + + Wrapper::new(&event, previous) + }; + + assert_impl_any_token( + previous._static_assertions_impl_any(), + ); + + event + }; + let event = check(); + + ::std::boxed::Box::pin( + (&&&&&&&Wrap::<&Adapter, _, IntoEvent>( + self, + &event, + ::std::marker::PhantomData, + )) + .get_tag() + .transform_event(self, event, ctx), + ) + } else { + unreachable!() + } + } } }; - let err = super::derive(input).unwrap_err(); + let shorter = super::derive(shorter_input).unwrap().to_string(); + let longer = super::derive(longer_input).unwrap().to_string(); - assert_eq!( - err.to_string(), - "either `into` or `transformed` argument of \ - `#[event(transformer)]` attribute is expected to be present, \ - but is absent", - ); + assert_eq!(shorter, longer); + assert_eq!(shorter, output.to_string()); } #[test] - fn errors_on_without_context_attribute() { + fn errors_on_without_event_attribute() { let input = parse_quote! { #[event( transformer( - adapter = Adapter, transformed = IntoEvent, + context = dyn Any, error = Infallible, ), )] - enum Event { - Event1(Event1), - Event2(Event2), - } + struct Adapter; }; let err = super::derive(input).unwrap_err(); assert_eq!( err.to_string(), - "either `context` or `ctx` argument of \ - `#[event(transformer)]` attribute is expected to be present, \ - but is absent", + "expected at least 1 `event` or `from` attribute", ); } #[test] - fn errors_on_without_error_attribute() { + fn errors_on_without_transformed_attribute() { let input = parse_quote! { #[event( transformer( - adapter = Adapter, - transformed = IntoEvent, - ctx = dyn Any, + event = Event, + context = dyn Any, + error = Infallible, ), )] enum Event { @@ -642,72 +1142,55 @@ mod spec { assert_eq!( err.to_string(), - "either `err` or `error` argument of \ + "either `into` or `transformed` argument of \ `#[event(transformer)]` attribute is expected to be present, \ but is absent", ); } #[test] - fn errors_on_multiple_fields_in_variant() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - enum Event { - Event1(Event1), - Event2 { - event: Event2, - second_field: Event3, - } - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!(err.to_string(), "enum variants must have exactly 1 field"); - } - - #[test] - fn errors_on_struct() { + fn errors_on_without_context_attribute() { let input = parse_quote! { #[event( transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, + from = Event, + transformed = IntoEvent, error = Infallible, ), )] - struct Event; + struct Adapter; }; let err = super::derive(input).unwrap_err(); - assert_eq!(err.to_string(), "expected enum only"); + assert_eq!( + err.to_string(), + "either `context` or `ctx` argument of \ + `#[event(transformer)]` attribute is expected to be present, \ + but is absent", + ); } #[test] - fn errors_on_empty_enum() { + fn errors_on_without_error_attribute() { let input = parse_quote! { #[event( transformer( - adapter = Adapter, + from = Event, into = IntoEvent, - context = dyn Any, - error = Infallible, + ctx = dyn Any, ), )] - enum Event {} + struct Adapter; }; let err = super::derive(input).unwrap_err(); - assert_eq!(err.to_string(), "enum must have at least one variant"); + assert_eq!( + err.to_string(), + "either `err` or `error` argument of \ + `#[event(transformer)]` attribute is expected to be present, \ + but is absent", + ); } } diff --git a/codegen/impl/src/es/event/versioned.rs b/codegen/impl/src/es/event/versioned.rs index 07ba7e5..7c8f920 100644 --- a/codegen/impl/src/es/event/versioned.rs +++ b/codegen/impl/src/es/event/versioned.rs @@ -120,9 +120,6 @@ impl Definition { /// /// [`Event::name`]: arcana_core::es::Event::name /// [`Event::version`]: arcana_core::es::Event::version - // TODO: replace `::std::concat!(...)` with `TypeId::of()` once it gets - // constified. - // https://github.com/rust-lang/rust/issues/77125 #[must_use] pub fn gen_uniqueness_glue_code(&self) -> TokenStream { let ty = &self.ident; diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index e28b864..7bda578 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -117,6 +117,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream { .into() } +// TODO describe specialization /// Macro for deriving [`Versioned`] on structs. /// /// For enums consisting of different [`Versioned`] events consider using @@ -154,17 +155,14 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .into() } -/// Macro for deriving [`Transformer`] on [`Adapter`]s to transform derived -/// [`Event`] enum. +/// Macro for deriving [`Transformer`] on [`Adapter`] to transform derived +/// [`Event`]s enums. /// /// # Struct attributes /// -/// #### `#[event(transformer(adapter = ))]` +/// #### `#[event(transformer(event = ))]` /// -/// [`Adapter`] to derive [`Transformer`] on. -/// -/// Provided [`Adapter`] must be able to [`Transformer::transform`][0] every -/// enum's variant. +/// [`Event`] to transform. /// /// #### `#[event(transformer(transformed = ))]` /// @@ -178,6 +176,17 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// /// [`Transformer::Error`][3] type for [`Transformer`] impl. /// +/// #### `#[event(transformer(max_number_of_variants = ))]` — optional +/// +/// Due to current limitations of const evaluation, we have to limit maximum +/// number of variants for transformed [`Event`]. Default value is +/// [`MAX_NUMBER_OF_VARIANTS`][4]. +/// +/// Realistically you should decrease this value if you want slightly shorter +/// compile time or increase it in case you have exceeded the default limit +/// (although it's recommended to refactor into sub-enums for better +/// readability). +/// /// # Example /// /// ```rust @@ -185,16 +194,16 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// # /// # use std::{any::Any, convert::Infallible}; /// # -/// # use arcana::es::adapter::transformer::{self, strategy, Transformer}; +/// # use arcana::es::{event, Event, adapter::Transformer}; /// # use derive_more::From; /// # -/// struct Adapter; -/// +/// #[derive(event::Versioned)] +/// #[event(name = "event.in", version = 1)] /// struct InputEvent; /// -/// impl transformer::WithStrategy for Adapter { -/// type Strategy = strategy::Into; -/// } +/// #[derive(event::Versioned)] +/// #[event(name = "event.out", version = 1)] +/// struct OutputEvent; /// /// impl From for OutputEvent { /// fn from(_: InputEvent) -> Self { @@ -202,88 +211,91 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// } /// } /// -/// struct OutputEvent; +/// #[derive(Event, From)] +/// enum InputEvents { +/// Input(InputEvent), +/// } +/// +/// #[derive(Event, From)] +/// enum OutputEvents { +/// Output(OutputEvent), +/// } /// -/// #[derive(From, Transformer)] +/// #[derive(Transformer)] /// #[event( /// transformer( -/// adapter = Adapter, +/// event = InputEvents, /// transformed = OutputEvents, /// context = dyn Any, /// error = Infallible, /// ) /// )] -/// enum InputEvents { -/// Input(InputEvent), -/// } -/// -/// #[derive(From)] -/// enum OutputEvents { -/// Output(OutputEvent), -/// } +/// struct Adapter; /// ``` /// -/// > __NOTE__: Single [`Event`] enum can be [`Transformer::transform`][0]ed by -/// > multiple [`Adapter`]s. +/// > __NOTE__: Single [`Adapter`] can [`Transformer::transform`][0] multiple +/// > [`Event`]s. /// /// ```rust /// # #![feature(generic_associated_types)] /// # /// # use std::{any::Any, convert::Infallible}; /// # -/// # use arcana::es::adapter::transformer::{self, strategy, Transformer}; +/// # use arcana::es::{event, Event, adapter::Transformer}; /// # use derive_more::From; /// # -/// # struct FirstAdapter; -/// # -/// # struct SecondAdapter; -/// # +/// # #[derive(event::Versioned)] +/// # #[event(name = "event", version = 1)] /// # struct InputEvent; /// # -/// # impl transformer::WithStrategy for FirstAdapter { -/// # type Strategy = strategy::Into; -/// # } +/// # #[derive(event::Versioned)] +/// # #[event(name = "out", version = 1)] +/// # struct OutputEvents; /// # -/// # impl transformer::WithStrategy for SecondAdapter { -/// # type Strategy = strategy::Into; +/// # #[derive(Event, From)] +/// # enum FirstInputEvents { +/// # Input(InputEvent), /// # } /// # -/// # impl From for OutputEvent { -/// # fn from(_: InputEvent) -> Self { -/// # OutputEvent -/// # } +/// # #[derive(Event, From)] +/// # enum SecondInputEvents { +/// # Input(InputEvent), /// # } /// # -/// # struct OutputEvent; -/// # -/// #[derive(From, Transformer)] +/// #[derive(Transformer)] +/// #[event( +/// transformer( +/// event(FirstInputEvents, SecondInputEvents), +/// transformed = OutputEvents, +/// context = dyn Any, +/// error = Infallible, +/// ) +/// )] +/// struct FirstAdapter; +/// +/// // equivalent to previous `derive` +/// #[derive(Transformer)] /// #[event( /// transformer( -/// adapter = FirstAdapter, +/// event = FirstInputEvents, /// transformed = OutputEvents, /// context = dyn Any, /// error = Infallible, /// ), /// transformer( -/// adapter = SecondAdapter, +/// event = SecondInputEvents, /// transformed = OutputEvents, /// context = dyn Any, /// error = Infallible, /// ), /// )] -/// enum InputEvents { -/// Input(InputEvent), -/// } -/// # -/// # #[derive(From)] -/// # enum OutputEvents { -/// # Output(OutputEvent), -/// # } +/// struct SecondAdapter; /// ``` /// [0]: arcana_core::es::adapter::Transformer::transform() /// [1]: arcana_core::es::adapter::Transformer::Transformed /// [2]: arcana_core::es::adapter::Transformer::Context /// [3]: arcana_core::es::adapter::Transformer::Error +/// [4]: arcana_codegen_impl::es::event::transformer::MAX_NUMBER_OF_VARIANTS /// [`Adapter`]: arcana_core::es::Adapter /// [`Event`]: trait@arcana_core::es::Event /// [`Transformer`]: arcana_core::es::adapter::Transformer diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 190965e..d65a4da 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -25,6 +25,4 @@ pub mod es; -pub use futures; - pub use static_assertions as sa; diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 3e1018b..8af4b61 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -178,12 +178,3 @@ where } } } - -#[cfg(feature = "codegen")] -pub mod codegen { - //! Re-exports for [`Transformer`] derive macro. - //! - //! [`Transformer`]: crate::es::adapter::Transformer - - pub use futures; -} diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index d358810..e4faf3f 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -7,6 +7,7 @@ use futures::Stream; #[doc(inline)] pub use strategy::Strategy; +// TODO: describe specialization /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -66,13 +67,65 @@ pub trait WithStrategy: Sized { type Strategy: Strategy; } -/// TODO +/// Marker trait indicating that [`Event`] can be transformed by [`Adapter`]. +/// +/// [`Adapter`]: crate::es::Adapter +/// [`Event`]: crate::es::Event pub trait TransformedBy {} impl TransformedBy for Ev where A: Transformer {} pub mod specialization { + //! [`Transformer`] machinery aiding codegen. + //! + //! This module describes traits used for + //! [auto-deref based specialization][1]. Basically we want to transform + //! [`Event`] using [`Transformer`] impl, if present. If not, fallback to + //! [`From`] impl, if present and, as last resort, entirely [`Skip`] it. + //! More detailed description of specialization levels provided below. + //! + //! # Specialization Order + //! + //! ## 1. [`TransformedBySkipAdapter`] + //! + //! If we are using [`Skip`] [`Strategy`] for transforming particular + //! [`Event`], this specialization allows to avoid implementation of + //! [`From`] [`Unknown`]. + //! + //! ## 2. [`TransformedByAdapter`] + //! + //! We prioritize custom [`Transformer`] implementations to allow custom + //! implementation of any [`Event`]. + //! + //! ## 3. [`TransformedByFrom`] + //! + //! If [`Event`] can trivially transformed into + //! [`Transformer::Transformed`][2] using [`From`] specialization, we do it. + //! + //! ## 4. [`TransformedByFromInitial`] + //! + //! If [`Event`] wrapped into [`event::Initial`] can trivially transformed + //! into [`Transformer::Transformed`][2] using [`From`] specialization, we + //! do it. + //! + //! ## 5. [`TransformedByFromUpcast`] + //! + //! TODO + //! + //! ## 6. [`TransformedByFromInitialUpcast`] + //! //! TODO + //! + //! ## 7. [`TransformedByEmpty`] + //! + //! If none of the above applies, we just [`Skip`]. + //! + //! [1]: https://bit.ly/3nrTbtj + //! [2]: super::Transformer::Transformed + //! [`Event`]: event::Event + //! [`Skip`]: strategy::Skip + //! [`Strategy`]: strategy::Strategy + //! [`Unknown`]: strategy::Unknown #![allow(clippy::unused_self)] @@ -87,32 +140,23 @@ pub mod specialization { }; use futures::{future, stream, StreamExt as _}; - /// TODO - pub trait Get { - /// TODO - type Out; - - /// TODO - fn get(&self) -> Option<&Self::Out>; - - /// TODO - fn unwrap(self) -> Self::Out; - } - - /// TODO - pub trait EnumSize { - /// TODO - const SIZE: usize; - } - - /// TODO + /// Wrapper to satisfy [orphan rules][1]. + /// + /// [1]: https://bit.ly/2Xea7bG #[derive(Debug)] pub struct Wrap( - /// TODO + /// [`Adapter`] to transform [`Event`]. + /// + /// [`Adapter`]: crate::es::Adapter + /// [`Event`]: crate::es::Event pub Adapter, - /// TODO + /// Transformed [`Event`]. + /// + /// [`Event`]: crate::es::Event pub Event, - /// TODO + /// Bound for [`Transformer::Transformed`][1]. + /// + /// [1]: super::Transformer::Transformed pub PhantomData, ); diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index 429751b..372dfe4 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -1,12 +1,6 @@ //! [`Strategy`] definition and default implementations. -use std::{ - any::Any, - convert::Infallible, - fmt::{Debug, Formatter}, - iter::Iterator, - marker::PhantomData, -}; +use std::{any::Any, convert::Infallible, iter::Iterator, marker::PhantomData}; use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; @@ -98,7 +92,7 @@ where type Transformed = event::Initial; type TransformedStream<'out> = stream::MapOk< InnerStrategy::TransformedStream<'out>, - WrapInitial, + WrapIntoInitial, >; fn transform<'me, 'ctx, 'out>( @@ -114,7 +108,7 @@ where } } -type WrapInitial = fn(Event) -> event::Initial; +type WrapIntoInitial = fn(Event) -> event::Initial; /// [`Strategy`] for skipping [`Event`]s. /// @@ -183,21 +177,8 @@ impl Strategy for AsIs { /// [`Strategy`] for converting [`Event`]s using [`From`] impl. /// /// [`Event`]: crate::es::Event -pub struct Into(PhantomData); - -impl Clone for Into { - fn clone(&self) -> Self { - Self(PhantomData) - } -} - -impl Copy for Into {} - -impl Debug for Into { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Into").finish() - } -} +#[derive(Clone, Copy, Debug)] +pub struct Into(PhantomData); impl Strategy for Into where @@ -226,6 +207,7 @@ where /// [`Splitter`] to define splitting logic. /// /// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] pub struct Split(PhantomData); /// Split single [`Event`] into multiple for [`Split`] [`Strategy`]. @@ -243,20 +225,6 @@ pub trait Splitter { fn split(&self, event: From) -> Self::Iterator; } -impl Clone for Split { - fn clone(&self) -> Self { - Self(PhantomData) - } -} - -impl Copy for Split {} - -impl Debug for Split { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Into").finish() - } -} - impl Strategy for Split where IntoEvent: 'static, diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 8e13063..9e0ac3e 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -226,6 +226,26 @@ pub mod codegen { use super::{Event, Initial}; + pub use futures; + + /// TODO + pub trait EnumSize { + /// TODO + const SIZE: usize; + } + + /// TODO + pub trait Get { + /// TODO + type Out; + + /// TODO + fn get(&self) -> Option<&Self::Out>; + + /// TODO + fn unwrap(self) -> Self::Out; + } + /// Custom [`Borrow`] codegen aiding trait for borrowing an [`Event`] either /// from itself or from an [`Initial`] wrapper. /// diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index baa9870..9952d1c 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -9,6 +9,7 @@ use std::array; use arcana::es::{event::Sourced, EventAdapter as _}; use futures::{stream, Stream, TryStreamExt as _}; +#[allow(clippy::semicolon_if_nothing_returned)] #[tokio::main] async fn main() { let mut chat = Option::::None; diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index b65823b..d0e8f16 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,7 +7,3 @@ pub use self::transformer::{TransformedBy, Transformer}; #[doc(inline)] pub use arcana_core::es::adapter::{Adapter, TransformedStream}; - -#[cfg(feature = "derive")] -#[doc(inline)] -pub use arcana_core::es::adapter::codegen; From 12f8a34e749ccbc74f1d6f1500273ee888855d61 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Sep 2021 11:38:10 +0300 Subject: [PATCH 060/104] WIP [skip ci] --- codegen/impl/src/es/event/mod.rs | 270 +++++++++++- codegen/impl/src/es/event/transformer.rs | 430 ++++++-------------- codegen/shim/src/lib.rs | 2 +- core/src/es/adapter/mod.rs | 109 +++-- core/src/es/adapter/transformer/mod.rs | 13 +- core/src/es/adapter/transformer/strategy.rs | 180 ++++---- examples/chat/src/main.rs | 3 + examples/chat/src/storage/chat.rs | 48 +-- examples/chat/src/storage/email.rs | 46 ++- examples/chat/src/storage/message.rs | 34 +- examples/chat/src/storage/mod.rs | 83 +--- src/es/adapter/mod.rs | 4 +- src/es/adapter/transformer/strategy.rs | 2 +- 13 files changed, 646 insertions(+), 578 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 605abb0..554248a 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -3,7 +3,7 @@ pub mod transformer; pub mod versioned; -use std::convert::TryFrom; +use std::{convert::TryFrom, iter}; use proc_macro2::TokenStream; use quote::quote; @@ -36,7 +36,12 @@ pub struct VariantAttrs { /// /// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))] +#[to_tokens(append( + impl_event, + impl_event_sourced, + gen_uniqueness_glue_code, + impl_transformer +))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, @@ -308,6 +313,267 @@ impl Definition { ); } } + + /// TODO + #[allow(clippy::too_many_lines)] + #[must_use] + pub fn impl_transformer(&self) -> TokenStream { + let event = &self.ident; + + let inner_match = self.inner_match(); + let transformed = self.transformed_stream(); + + let mut generics = self.generics.clone(); + generics.params.push(parse_quote! { __A }); + generics + .make_where_clause() + .predicates + .push(parse_quote! { __A: ::arcana::es::adapter::WithError }); + generics.make_where_clause().predicates.push({ + let adapter_constrains = self.variants.iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + .map(|ty| { + quote! { + ::arcana::es::adapter::Transformer< + #ty, + Context = <__A as ::arcana::es::adapter::WithError>:: + Context, + > + } + }); + let self_bound = quote! { Self: #( #adapter_constrains )+* }; + parse_quote! { #self_bound } + }); + generics.make_where_clause().predicates.push({ + let adapter_constrains = self + .variants + .iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + .map(|ty| { + quote! { + ::std::convert::From< + >:: + Transformed + > + } + }); + let transformed_bound = quote! { + <__A as ::arcana::es::adapter::WithError>::Transformed: + #( #adapter_constrains )+* + }; + parse_quote! { #transformed_bound + 'static } + }); + generics.make_where_clause().predicates.push({ + let adapter_constrains = self + .variants + .iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + .map(|ty| { + quote! { + ::std::convert::From< + >:: + Error + > + } + }); + let err_bound = quote! { + <__A as ::arcana::es::adapter::WithError>::Error: + #( #adapter_constrains )+* + }; + parse_quote! { #err_bound + 'static } + }); + + for bound in self + .variants + .iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + .map(|ty| { + parse_quote! { + >::Error: + 'static + } + }) + { + generics.make_where_clause().predicates.push(bound); + } + for bound in self + .variants + .iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + .map(|ty| { + parse_quote! { + >:: + Transformed: 'static + } + }) + { + generics.make_where_clause().predicates.push(bound); + } + + let (_, type_gen, _) = self.generics.split_for_impl(); + let (impl_gen, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl#impl_gen ::arcana::es::adapter::Transformer< + #event#type_gen + > for ::arcana::es::adapter::Wrapper<__A> #where_clause + { + type Context = <__A as ::arcana::es::adapter::WithError>:: + Context; + type Error = <__A as ::arcana::es::adapter::WithError>::Error; + type Transformed = <__A as ::arcana::es::adapter::WithError>:: + Transformed; + type TransformedStream<'out> = #transformed; + + fn transform<'me, 'ctx, 'out>( + &'me self, + __event: #event, + __context: &'ctx <__A as ::arcana::es::adapter::WithError>:: + Context, + ) -> >:: + TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + match __event { + #inner_match + } + } + } + } + } + + /// Generates code of [`Transformer::Transformed`][0] associated type. + /// + /// This is basically a recursive type + /// [`Either`]`>`, where every `VarN` is an + /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a + /// [`stream::Map`] with a function that uses [`From`] impl to transform + /// [`Event`]s into compatible ones. + /// + /// [0]: arcana_core::es::adapter::Transformer::Transformed + /// [1]: arcana_core::es::adapter::Transformer::TransformedStream + /// [`Either`]: futures::future::Either + /// [`Event`]: trait@::arcana_core::es::Event + /// [`stream::Map`]: futures::stream::Map + #[must_use] + pub fn transformed_stream(&self) -> TokenStream { + let from = &self.ident; + + let transformed_stream = |event: &syn::Type| { + quote! { + ::arcana::es::adapter::codegen::futures::stream::Map< + >:: + TransformedStream<'out>, + fn( + ::std::result::Result< + >::Transformed, + >::Error, + >, + ) -> ::std::result::Result< + >::Transformed, + >::Error, + > + > + } + }; + + self.variants + .iter() + .rev() + .filter_map(|var| var.fields.iter().next()) + .fold(None, |acc, field| { + let variant_stream = transformed_stream(&field.ty); + Some( + acc.map(|acc| { + quote! { + ::arcana::es::adapter::codegen::futures::future:: + Either< + #variant_stream, + #acc, + > + } + }) + .unwrap_or(variant_stream), + ) + }) + .unwrap_or_default() + } + + /// Generates code for implementation of a [`Transformer::transform()`][0] + /// fn. + /// + /// Generated code matches over every [`Event`]'s variant and makes it + /// compatible with [`Self::transformed_stream()`] type with + /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] + /// combinators. + /// + /// [0]: arcana_core::es::adapter::Transformer::transform + /// [`Event`]: trait@arcana_core::es::Event + /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() + /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() + #[must_use] + pub fn inner_match(&self) -> TokenStream { + let event = &self.ident; + + self.variants + .iter() + .filter_map(|var| { + var.fields.iter().next().map(|f| (&var.ident, &f.ty)) + }) + .enumerate() + .map(|(i, (variant_ident, var_ty))| { + let stream_map = quote! { + ::arcana::es::adapter::codegen::futures::StreamExt::map( + >:: + transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + }; + + let right_stream = quote! { + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream + }; + let left_stream = quote! { + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream + }; + let left_stream_count = + (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); + + let transformed_stream = iter::repeat(left_stream) + .take(left_stream_count) + .chain(iter::repeat(right_stream).take(i)) + .fold(stream_map, |acc, stream| { + quote! { #stream(#acc) } + }); + + quote! { + #event::#variant_ident(__event) => { + #transformed_stream + }, + } + }) + .collect() + } } #[cfg(test)] diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index abde309..e6ff60b 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -1,10 +1,14 @@ //! `#[derive(adapter::Transformer)]` macro implementation. -use std::{convert::TryFrom, iter}; +use std::{collections::HashMap, convert::TryFrom, iter}; use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token, +}; use synthez::{ParseAttrs, Required, Spanning, ToTokens}; /// Expands `#[derive(adapter::Transformer)]` macro. @@ -28,65 +32,104 @@ pub struct Attrs { /// /// [0]: arcana_core::es::adapter::Transformer #[parse(nested)] - pub transformer: Vec>, + pub transformer: Required>, } -/// Inner attributes of `#[event(transformer(...)]`. -/// -/// Each of them used to generate [`Transformer`][0] trait impl. -/// -/// [0]: arcana_core::es::adapter::Transformer -#[derive(Debug, Default, ParseAttrs)] -pub struct InnerAttrs { - /// Type to derive [`Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - #[parse(value)] - pub adapter: Required, +/// TODO +#[derive(Debug, Default, PartialEq)] +pub struct StrategyAttr { + /// TODO + pub strategies: HashMap>, +} - /// [`Transformer::Transformed`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - #[parse(value, alias = into)] - pub transformed: Required, +/// TODO +#[derive(Debug)] +pub struct StrategyAttrRepr { + /// TODO + pub strategy: syn::Type, - /// [`Transformer::Context`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Context - #[parse(value, alias = ctx)] - pub context: Required, + /// TODO + pub eq_sign: syn::Token![=], - /// [`Transformer::Error`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Error - #[parse(value, alias = err)] - pub error: Required, + /// TODO + pub greater_sign: syn::Token![>], + + /// TODO + pub events: EventsRepr, +} + +/// TODO +#[derive(Debug)] +pub enum EventsRepr { + /// TODO + Many { + /// TODO + paren: token::Paren, + + /// TODO + events: Punctuated, + }, + + /// TODO + Single { + /// TODO + event: syn::Type, + }, } -impl From for ImplDefinition { - fn from(attrs: InnerAttrs) -> Self { - let InnerAttrs { - adapter, - transformed, - context, - error, - } = attrs; - Self { - adapter: adapter.into_inner(), - transformed: transformed.into_inner(), - context: context.into_inner(), - error: error.into_inner(), - } +impl Parse for StrategyAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let parse_attr = |input: ParseStream<'_>| { + let many = || { + let content; + Ok(EventsRepr::Many { + paren: syn::parenthesized!(content in input), + events: content.parse_terminated(syn::Type::parse)?, + }) + }; + let single_or_many = || { + many().or_else(|_| -> syn::Result<_> { + Ok(EventsRepr::Single { + event: input.parse()?, + }) + }) + }; + + Ok(StrategyAttrRepr { + strategy: input.parse()?, + eq_sign: input.parse()?, + greater_sign: input.parse()?, + events: single_or_many()?, + }) + }; + + let strategies = input + .parse_terminated::<_, syn::Token![,]>(parse_attr)? + .into_iter() + .map(|repr: StrategyAttrRepr| { + let events = match repr.events { + EventsRepr::Many { events, .. } => { + events.into_iter().collect() + } + EventsRepr::Single { event } => vec![event], + }; + (repr.strategy, events) + }) + .collect::>(); + + Ok(Self { strategies }) } } -// TODO: add PartialEq impls in synthez -impl PartialEq for InnerAttrs { - fn eq(&self, other: &Self) -> bool { - *self.adapter == *other.adapter - && *self.transformed == *other.transformed - && *self.context == *other.context - && *self.error == *other.error +impl ParseAttrs for StrategyAttr { + fn try_merge(self, another: Self) -> syn::Result { + Ok(Self { + strategies: self + .strategies + .into_iter() + .chain(another.strategies.into_iter()) + .collect(), + }) } } @@ -95,297 +138,54 @@ impl PartialEq for InnerAttrs { /// /// [0]: arcana_core::es::adapter::Transformer #[derive(Debug, ToTokens)] -#[to_tokens(append(derive_transformer))] +#[to_tokens(append(impl_strategies))] pub struct Definition { /// Generic parameter of the [`Transformer`][0]. /// /// [0]: arcana_core::es::adapter::Transformer - pub event: syn::Ident, + pub adapter: syn::Ident, /// [`syn::Generics`] of this enum's type. pub generics: syn::Generics, - /// [`struct@syn::Ident`] and single [`syn::Type`] of every - /// [`syn::FieldsUnnamed`] [`syn::Variant`]. - pub variants: Vec<(syn::Ident, syn::Type)>, - - /// Definitions of structures to derive [`Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - pub transformers: Vec, -} - -/// Representation of a struct implementing [`Transformer`][0], used for code -/// generation. -/// -/// [0]: arcana_core::es::adapter::Transformer -#[derive(Debug)] -pub struct ImplDefinition { - /// Type to derive [`Transformer`][0] on. - /// - /// [0]: arcana_core::es::adapter::Transformer - pub adapter: syn::Type, - - /// [`Transformer::Transformed`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - pub transformed: syn::Type, - - /// [`Transformer::Context`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Context - pub context: syn::Type, - - /// [`Transformer::Error`][0] type. - /// - /// [0]: arcana_core::es::adapter::Transformer::Error - pub error: syn::Type, + /// TODO + pub strategies: HashMap>, } impl TryFrom for Definition { type Error = syn::Error; fn try_from(input: syn::DeriveInput) -> syn::Result { - let span = input.span(); - let attrs: Attrs = Attrs::parse_attrs("event", &input)?; - - if attrs.transformer.is_empty() { - return Err(syn::Error::new( - input.span(), - "expected at least 1 `#[event(transformer(...))` attribute", - )); - } - - let data = if let syn::Data::Enum(data) = input.data { - data - } else { - return Err(syn::Error::new(input.span(), "expected enum only")); - }; - - let variants = data - .variants - .into_iter() - .map(Self::parse_variant) - .collect::>>()?; - if variants.is_empty() { - return Err(syn::Error::new( - span, - "enum must have at least one variant", - )); - } - - let transformers = attrs - .transformer - .into_iter() - .map(|tr| tr.into_inner().into()) - .collect(); + let attrs: StrategyAttr = + StrategyAttr::parse_attrs("transformer", &input)?; Ok(Self { - event: input.ident, + adapter: input.ident, generics: input.generics, - variants, - transformers, + strategies: attrs.strategies, }) } } impl Definition { - /// Parses [`syn::Variant`], returning its [`syn::Ident`] and single inner - /// [`syn::Field`]. - /// - /// # Errors - /// - /// If [`syn::Variant`] doesn't have exactly one unnamed 1 [`syn::Field`]. - fn parse_variant( - variant: syn::Variant, - ) -> syn::Result<(syn::Ident, syn::Type)> { - if variant.fields.len() != 1 { - return Err(syn::Error::new( - variant.span(), - "enum variants must have exactly 1 field", - )); - } - if !matches!(variant.fields, syn::Fields::Unnamed(_)) { - return Err(syn::Error::new( - variant.span(), - "only tuple struct enum variants allowed", - )); - } - - Ok((variant.ident, variant.fields.into_iter().next().unwrap().ty)) - } - - /// Generates code to derive [`Transformer`][0] trait. - /// - /// [0]: arcana_core::es::adapter::Transformer + /// TODO #[must_use] - pub fn derive_transformer(&self) -> TokenStream { - let event = &self.event; - - self.transformers.iter().map(|tr| { - let ImplDefinition { - adapter, - transformed, - context, - error, - } = tr; - let inner_match = self.inner_match(adapter); - let transformed_stream = self.transformed_stream(adapter); - let (impl_gen, type_gen, where_clause) = - self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl #impl_gen ::arcana::es::adapter::Transformer< - #event#type_gen - > for #adapter #where_clause - { - type Context = #context; - type Error = #error; - type Transformed = #transformed; - type TransformedStream<'me, 'ctx> = #transformed_stream; - - fn transform<'me, 'ctx>( - &'me self, - __event: #event, - __context: - &'ctx >::Context, - ) -> >:: - TransformedStream<'me, 'ctx> - { - match __event { - #inner_match - } - } - } - } - }) - .collect() - } + pub fn impl_strategies(&self) -> TokenStream { + let (impl_gen, type_gen, where_cl) = self.generics.split_for_impl(); + let adapter = &self.adapter; - /// Generates code of [`Transformer::Transformed`][0] associated type. - /// - /// This is basically a recursive type - /// [`Either`]`>`, where every `VarN` is an - /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a - /// [`stream::Map`] with a function that uses [`From`] impl to transform - /// [`Event`]s into compatible ones. - /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - /// [1]: arcana_core::es::adapter::Transformer::TransformedStream - /// [`Either`]: futures::future::Either - /// [`Event`]: trait@::arcana_core::es::Event - /// [`stream::Map`]: futures::stream::Map - #[must_use] - pub fn transformed_stream(&self, adapter: &syn::Type) -> TokenStream { - let from = &self.event; - - let transformed_stream = |event: &syn::Type| { - quote! { - ::arcana::es::adapter::codegen::futures::stream::Map< - <#adapter as ::arcana::es::adapter::Transformer<#event >>:: - TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - <#adapter as ::arcana::es::adapter:: - Transformer<#event >>::Transformed, - <#adapter as ::arcana::es::adapter:: - Transformer<#event >>::Error, - >, - ) -> ::std::result::Result< - <#adapter as ::arcana::es::adapter:: - Transformer<#from>>::Transformed, - <#adapter as ::arcana::es::adapter:: - Transformer<#from>>::Error, - >, - > - } - }; - - self.variants + self.strategies .iter() - .rev() - .fold(None, |acc, (_, var_ty)| { - let variant_stream = transformed_stream(var_ty); - Some( - acc.map(|acc| { - quote! { - ::arcana::es::adapter::codegen::futures::future:: - Either< - #variant_stream, - #acc, - > - } - }) - .unwrap_or(variant_stream), - ) + .flat_map(|(strategy, events)| { + events.iter().zip(iter::repeat(strategy)) }) - .unwrap_or_default() - } - - /// Generates code for implementation of a [`Transformer::transform()`][0] - /// fn. - /// - /// Generated code matches over every [`Event`]'s variant and makes it - /// compatible with [`Self::transformed_stream()`] type with - /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] - /// combinators. - /// - /// [0]: arcana_core::es::adapter::Transformer::transform - /// [`Event`]: trait@arcana_core::es::Event - /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() - /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() - #[must_use] - pub fn inner_match(&self, adapter: &syn::Type) -> TokenStream { - let event = &self.event; - - self.variants - .iter() - .enumerate() - .map(|(i, (variant_ident, variant_ty))| { - let stream_map = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt::map( - <#adapter as ::arcana::es::adapter::Transformer< - #variant_ty - > >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) - }; - - let right_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - right_stream - }; - let left_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - left_stream - }; - let left_stream_count = - (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); - - let transformed_stream = iter::repeat(left_stream) - .take(left_stream_count) - .chain(iter::repeat(right_stream).take(i)) - .fold(stream_map, |acc, stream| { - quote! { #stream(#acc) } - }); - + .map(|(ev, strategy)| { quote! { - #event::#variant_ident(__event) => { - #transformed_stream - }, + impl#impl_gen ::arcana::es::adapter::transformer:: + WithStrategy<#ev> for #adapter#type_gen #where_cl + { + type Strategy = #strategy; + } } }) .collect() diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 8bbb012..d132281 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -287,7 +287,7 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// [`Adapter`]: arcana_core::es::Adapter /// [`Event`]: trait@arcana_core::es::Event /// [`Transformer`]: arcana_core::es::adapter::Transformer -#[proc_macro_derive(EventTransformer, attributes(event))] +#[proc_macro_derive(EventTransformer, attributes(transformer))] pub fn derive_event_transformer(input: TokenStream) -> TokenStream { codegen::es::event::transformer::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 778fba2..bc67c6c 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -10,10 +10,34 @@ use std::{ use futures::{future, stream, Stream, StreamExt as _}; use pin_project::pin_project; +use ref_cast::RefCast; #[doc(inline)] pub use self::transformer::Transformer; +/// TODO +#[derive(Debug, RefCast)] +#[repr(transparent)] +pub struct Wrapper(pub A); + +impl WithError for Wrapper { + type Context = A::Context; + type Error = A::Error; + type Transformed = A::Transformed; +} + +/// TODO +pub trait WithError { + /// TODO + type Context: ?Sized; + + /// TODO + type Error; + + /// TODO + type Transformed; +} + /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -46,44 +70,56 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me, 'ctx>( + fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } -impl Adapter for T +impl Adapter for A where - Events: Stream, - T: Transformer + 'static, - T::Context: 'static, + Events: Stream + 'static, + A: WithError, + Wrapper: Transformer + 'static, + as Transformer>::Context: 'static, + ::Transformed: + From< as Transformer>::Transformed>, + ::Error: + From< as Transformer>::Error>, { - type Context = T::Context; - type Error = T::Error; - type Transformed = T::Transformed; - type TransformedStream<'me, 'ctx> = TransformedStream<'me, 'ctx, T, Events>; + type Context = as Transformer>::Context; + type Error = ::Error; + type Transformed = ::Transformed; + type TransformedStream<'out> = TransformedStream<'out, Wrapper, Events>; - fn transform_all<'me, 'ctx>( + fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { - TransformedStream::new(self, events, context) + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + TransformedStream::new(RefCast::ref_cast(self), events, context) } } #[pin_project] /// [`Stream`] for [`Adapter`] blanket impl. -pub struct TransformedStream<'adapter, 'ctx, Adapter, Events> +pub struct TransformedStream<'out, Adapter, Events> where Events: Stream, Adapter: Transformer, @@ -91,14 +127,12 @@ where #[pin] events: Events, #[pin] - transformed_stream: - AdapterTransformedStream<'adapter, 'ctx, Events::Item, Adapter>, - adapter: &'adapter Adapter, - context: &'ctx Adapter::Context, + transformed_stream: AdapterTransformedStream<'out, Events::Item, Adapter>, + adapter: &'out Adapter, + context: &'out Adapter::Context, } -impl<'adapter, 'ctx, Adapter, Events> Debug - for TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> Debug for TransformedStream<'out, Adapter, Events> where Events: Debug + Stream, Adapter: Debug + Transformer, @@ -113,8 +147,8 @@ where } } -type AdapterTransformedStream<'adapter, 'ctx, Event, Adapter> = future::Either< - >::TransformedStream<'adapter, 'ctx>, +type AdapterTransformedStream<'out, Event, Adapter> = future::Either< + >::TransformedStream<'out>, stream::Empty< Result< >::Transformed, @@ -123,16 +157,15 @@ type AdapterTransformedStream<'adapter, 'ctx, Event, Adapter> = future::Either< >, >; -impl<'adapter, 'ctx, Adapter, Events> - TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> TransformedStream<'out, Adapter, Events> where Events: Stream, Adapter: Transformer, { fn new( - adapter: &'adapter Adapter, + adapter: &'out Adapter, events: Events, - context: &'ctx Adapter::Context, + context: &'out Adapter::Context, ) -> Self { Self { events, @@ -143,13 +176,19 @@ where } } -impl<'adapter, 'ctx, Adapter, Events> Stream - for TransformedStream<'adapter, 'ctx, Adapter, Events> +impl<'out, Adapter, Events> Stream for TransformedStream<'out, Adapter, Events> where Events: Stream, - Adapter: Transformer, + Adapter: Transformer + WithError, + ::Transformed: + From<>::Transformed>, + ::Error: + From<>::Error>, { - type Item = Result; + type Item = Result< + ::Transformed, + ::Error, + >; fn poll_next( self: Pin<&mut Self>, @@ -161,7 +200,9 @@ where let res = futures::ready!(this.transformed_stream.as_mut().poll_next(cx)); if let Some(ev) = res { - return Poll::Ready(Some(ev)); + return Poll::Ready(Some( + ev.map(Into::into).map_err(Into::into), + )); } let res = futures::ready!(this.events.as_mut().poll_next(cx)); diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 299652d..16d30ce 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -39,19 +39,22 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( &'me self, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } /// Instead of implementing [`Transformer`] manually, you can use this trait diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index a7040aa..35dfefd 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -1,16 +1,13 @@ //! [`Strategy`] definition and default implementations. use std::{ - any::Any, - convert::Infallible, - fmt::{Debug, Formatter}, - iter::Iterator, + any::Any, convert::Infallible, fmt::Debug, iter::Iterator, marker::PhantomData, }; use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; -use crate::es::event; +use crate::es::{adapter, event}; use super::{Transformer, WithStrategy}; @@ -33,42 +30,54 @@ pub trait Strategy { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'me, 'ctx>: Stream< - Item = Result, - >; + #[rustfmt::skip] + type TransformedStream<'out>: + Stream> + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; } -impl Transformer for Adapter +impl Transformer for adapter::Wrapper where - Adapter: WithStrategy, + Event: event::Versioned, + Adapter: WithStrategy + adapter::WithError, Adapter::Strategy: Strategy, + ::Transformed: + From<>::Transformed>, + ::Error: + From<>::Error>, { type Context = >::Context; type Error = >::Error; type Transformed = >::Transformed; - type TransformedStream<'me, 'ctx> = = >::TransformedStream<'me, 'ctx>; + >>::TransformedStream<'out>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( &'me self, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { >::transform( - self, event, context, + &self.0, event, context, ) } } @@ -78,26 +87,32 @@ where /// [`Event`]: crate::es::Event /// [`Initial`]: event::Initial #[derive(Clone, Debug)] -pub struct Initialized(PhantomData); +pub struct Initialized(PhantomData); impl Strategy for Initialized where InnerStrategy: Strategy, + InnerStrategy::Transformed: 'static, + InnerStrategy::Error: 'static, { type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = event::Initial; - type TransformedStream<'me, 'ctx> = stream::MapOk< - InnerStrategy::TransformedStream<'me, 'ctx>, + type TransformedStream<'out> = stream::MapOk< + InnerStrategy::TransformedStream<'out>, WrapInitial, >; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, context: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) } } @@ -115,26 +130,27 @@ type WrapInitial = fn(Event) -> event::Initial; #[derive(Clone, Copy, Debug)] pub struct Skip; -/// As [`Skip`] [`Strategy`] isn't parametrised by [`Strategy::Transformed`] -/// [`Event`], this type expresses 'never going to be constructed'. -/// -/// [`Event`]: crate::es::Event -// TODO: replace with `never`(`!`), once it's stabilized. -#[derive(Clone, Copy, Debug)] -pub enum Unknown {} - -impl Strategy for Skip { +impl Strategy for Skip +where + Adapter: adapter::WithError, + Adapter::Transformed: 'static, + Adapter::Error: 'static, +{ type Context = dyn Any; - type Error = Infallible; - type Transformed = Unknown; - type TransformedStream<'me, 'ctx> = - stream::Empty>; + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'out> = + stream::Empty>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( _: &'me Adapter, _: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::empty() } } @@ -149,14 +165,18 @@ impl Strategy for AsIs { type Context = dyn Any; type Error = Infallible; type Transformed = Event; - type TransformedStream<'me, 'ctx> = - stream::Once>>; + type TransformedStream<'out> = + stream::Once>>; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( _: &'me Adapter, event: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::once(future::ready(Ok(event))) } } @@ -164,45 +184,45 @@ impl Strategy for AsIs { /// [`Strategy`] for converting [`Event`]s using [`From`] impl. /// /// [`Event`]: crate::es::Event -pub struct Into(PhantomData); +#[derive(Copy, Clone, Debug)] +pub struct Into(PhantomData<(I, InnerStrategy)>); -impl Clone for Into { - fn clone(&self) -> Self { - Self(PhantomData) - } -} - -impl Copy for Into {} - -impl Debug for Into { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Into").finish() - } -} - -impl Strategy for Into +impl Strategy + for Into where - IntoEvent: From + 'static, + InnerStrategy: Strategy, + InnerStrategy::Transformed: 'static, + InnerStrategy::Error: 'static, + IntoEvent: From + 'static, { - type Context = dyn Any; - type Error = Infallible; + type Context = InnerStrategy::Context; + type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'me, 'ctx> = - stream::Once>>; + type TransformedStream<'out> = stream::MapOk< + InnerStrategy::TransformedStream<'out>, + IntoFn, + >; - fn transform<'me, 'ctx>( - _: &'me Adapter, + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, event: Event, - _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { - stream::once(future::ready(Ok(IntoEvent::from(event)))) + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } } +type IntoFn = fn(FromEvent) -> IntoEvent; + /// [`Strategy`] for splitting single [`Event`] into multiple. Implement /// [`Splitter`] to define splitting logic. /// /// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] pub struct Split(PhantomData); /// Split single [`Event`] into multiple for [`Split`] [`Strategy`]. @@ -220,34 +240,26 @@ pub trait Splitter { fn split(&self, event: From) -> Self::Iterator; } -impl Clone for Split { - fn clone(&self) -> Self { - Self(PhantomData) - } -} - -impl Copy for Split {} - -impl Debug for Split { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Into").finish() - } -} - impl Strategy for Split where + IntoEvent: 'static, Adapter: Splitter, + Adapter::Iterator: 'static, { type Context = dyn Any; type Error = Infallible; type Transformed = ::Item; - type TransformedStream<'me, 'ctx> = SplitStream; + type TransformedStream<'out> = SplitStream; - fn transform<'me, 'ctx>( + fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, _: &'ctx Self::Context, - ) -> Self::TransformedStream<'me, 'ctx> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { stream::iter(adapter.split(event)).map(Ok) } } diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 51c4cec..0e61fa3 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -24,6 +24,7 @@ async fn main() { .unwrap(); println!("{:?}", chat_events); + assert_eq!(chat_events.len(), 4); assert_eq!( chat, Some(domain::Chat { @@ -40,6 +41,7 @@ async fn main() { .unwrap(); println!("{:?}", email_events); + assert_eq!(email_events.len(), 2); assert_eq!( email, Some(domain::Email { @@ -56,6 +58,7 @@ async fn main() { .unwrap(); println!("{:?}", message_events); + assert_eq!(message_events.len(), 1); assert_eq!(message, Some(domain::Message)); } diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index fc43386..b6fb594 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,37 +1,39 @@ -use arcana::es::{ - adapter::transformer::{self, strategy}, - event::Sourcing, -}; +use std::{any::Any, convert::Infallible}; -use crate::{domain, event}; +use arcana::es::adapter::{ + self, + transformer::{self, strategy}, + Transformer, +}; -#[derive(Debug)] -pub struct Adapter; +use crate::event; -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; +impl adapter::WithError for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = event::Chat; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} +#[derive(Debug, Transformer)] +#[transformer( + strategy::Initialized => ( + event::chat::public::Created, + event::chat::private::Created, + ), + strategy::AsIs => event::message::Posted, + strategy::Skip => ( + event::email::v1::AddedAndConfirmed, + event::email::Confirmed, + event::email::Added, + ) +)] +pub struct Adapter; impl transformer::WithStrategy for Adapter { type Strategy = strategy::Initialized>; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter -where - Ev: Sourcing, -{ - type Strategy = strategy::AsIs; -} - // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 8ac34a9..da66b2f 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,26 +1,35 @@ -use std::array; +use std::{any::Any, array, convert::Infallible}; use arcana::es::{ - adapter::transformer::{self, strategy}, - event::{Initial, Sourcing}, + adapter::{ + self, + transformer::{self, strategy}, + Transformer, + }, + event::Initial, }; use either::Either; -use crate::{domain, event}; +use crate::event; -pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; +impl adapter::WithError for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = event::Email; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; -} +#[derive(Debug, Transformer)] +#[transformer( + strategy::Initialized => event::email::Added, + strategy::AsIs => event::email::Confirmed, + strategy::Skip => ( + event::chat::public::Created, + event::chat::private::Created, + event::chat::v1::Created, + event::message::Posted, + ) +)] +pub struct Adapter; impl transformer::WithStrategy for Adapter @@ -72,10 +81,3 @@ impl From> } } } - -impl transformer::WithStrategy for Adapter -where - Ev: Sourcing, -{ - type Strategy = strategy::AsIs; -} diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 4c4a065..c5a2c52 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,17 +1,27 @@ -use arcana::es::adapter::transformer::{self, strategy}; +use std::{any::Any, convert::Infallible}; -use crate::event; - -pub struct Adapter; +use arcana::es::adapter::{self, transformer::strategy, Transformer}; -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} +use crate::event; -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Initialized; +impl adapter::WithError for Adapter { + type Context = dyn Any; + type Error = Infallible; + type Transformed = event::Message; } -impl transformer::WithStrategy for Adapter { - type Strategy = strategy::Skip; -} +#[derive(Debug, Transformer)] +#[transformer( + strategy::Initialized => ( + event::message::Posted, + ), + strategy::Skip => ( + event::chat::public::Created, + event::chat::private::Created, + event::chat::v1::Created, + event::email::Added, + event::email::Confirmed, + event::email::v1::AddedAndConfirmed, + ), +)] +pub struct Adapter; diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 30bc438..6752beb 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -2,106 +2,33 @@ pub mod chat; pub mod email; pub mod message; -use std::{any::Any, convert::Infallible}; - -use arcana::es::{ - self, - adapter::{transformer::strategy, Transformer}, -}; +use arcana::es; use derive_more::From; use crate::event; -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = email::Adapter, - into = event::Email, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = message::Adapter, - into = event::Message, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum Event { Chat(ChatEvent), Message(MessageEvent), Email(EmailEvent), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), PublicCreated(event::chat::public::Created), PrivateCreated(event::chat::private::Created), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = chat::Adapter, - into = event::Chat, - ctx = dyn Any, - err = Infallible, - ), - transformer( - adapter = message::Adapter, - into = event::Message, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum MessageEvent { Posted(event::message::Posted), } -#[derive(Debug, es::Event, From, Transformer)] -#[event( - transformer( - adapter = email::Adapter, - into = event::Email, - ctx = dyn Any, - err = Infallible, - ), -)] +#[derive(Debug, es::Event, From)] pub enum EmailEvent { Added(event::email::Added), Confirmed(event::email::Confirmed), AddedAndConfirmed(event::email::v1::AddedAndConfirmed), } - -impl From for event::Chat { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -impl From for event::Email { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} - -impl From for event::Message { - fn from(u: strategy::Unknown) -> Self { - match u {} - } -} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index b59bda7..c9616f2 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -6,7 +6,9 @@ pub mod transformer; pub use self::transformer::Transformer; #[doc(inline)] -pub use arcana_core::es::adapter::{Adapter, TransformedStream}; +pub use arcana_core::es::adapter::{ + Adapter, TransformedStream, WithError, Wrapper, +}; #[cfg(feature = "derive")] #[doc(inline)] diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index 01537de..589a35a 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,5 +2,5 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Initialized, Into, Skip, Split, Splitter, Strategy, Unknown, + AsIs, Initialized, Into, Skip, Split, Splitter, Strategy, }; From d1e96b2ccc648ef8bcacc85d32b803f64b35144e Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Sep 2021 12:12:26 +0300 Subject: [PATCH 061/104] WIP [skip ci] --- codegen/impl/Cargo.toml | 1 - codegen/shim/src/lib.rs | 146 +------- core/src/es/adapter/transformer/mod.rs | 388 +------------------- core/src/es/adapter/transformer/strategy.rs | 18 +- examples/chat/src/event/chat.rs | 10 - src/es/adapter/transformer/mod.rs | 9 +- src/es/event.rs | 3 +- 7 files changed, 25 insertions(+), 550 deletions(-) diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index e5be615..574fcc0 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -17,7 +17,6 @@ readme = "README.md" doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] -either = "1.6.1" proc-macro2 = { version = "1.0.4", default-features = false } quote = { version = "1.0.9", default-features = false } syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 8ba29f8..cd6e402 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -117,7 +117,6 @@ pub fn derive_event(input: TokenStream) -> TokenStream { .into() } -// TODO describe specialization /// Macro for deriving [`Versioned`] on structs. /// /// For enums consisting of different [`Versioned`] events consider using @@ -155,150 +154,7 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .into() } -/// Macro for deriving [`Transformer`] on [`Adapter`] to transform derived -/// [`Event`]s enums. -/// -/// # Struct attributes -/// -/// #### `#[event(transformer(event = ))]` -/// -/// [`Event`] to transform. -/// -/// #### `#[event(transformer(transformed = ))]` -/// -/// [`Transformer::Transformed`][1] type for [`Transformer`] impl. -/// -/// #### `#[event(transformer(context = ))]` -/// -/// [`Transformer::Context`][2] type for [`Transformer`] impl. -/// -/// #### `#[event(transformer(error = ))]` -/// -/// [`Transformer::Error`][3] type for [`Transformer`] impl. -/// -/// #### `#[event(transformer(max_number_of_variants = ))]` — optional -/// -/// Due to current limitations of const evaluation, we have to limit maximum -/// number of variants for transformed [`Event`]. Default value is -/// [`MAX_NUMBER_OF_VARIANTS`][4]. -/// -/// Realistically you should decrease this value if you want slightly shorter -/// compile time or increase it in case you have exceeded the default limit -/// (although it's recommended to refactor into sub-enums for better -/// readability). -/// -/// # Example -/// -/// ```rust -/// # #![feature(generic_associated_types)] -/// # -/// # use std::{any::Any, convert::Infallible}; -/// # -/// # use arcana::es::{event, Event, adapter::Transformer}; -/// # use derive_more::From; -/// # -/// #[derive(event::Versioned)] -/// #[event(name = "event.in", version = 1)] -/// struct InputEvent; -/// -/// #[derive(event::Versioned)] -/// #[event(name = "event.out", version = 1)] -/// struct OutputEvent; -/// -/// impl From for OutputEvent { -/// fn from(_: InputEvent) -> Self { -/// OutputEvent -/// } -/// } -/// -/// #[derive(Event, From)] -/// enum InputEvents { -/// Input(InputEvent), -/// } -/// -/// #[derive(Event, From)] -/// enum OutputEvents { -/// Output(OutputEvent), -/// } -/// -/// #[derive(Transformer)] -/// #[event( -/// transformer( -/// event = InputEvents, -/// transformed = OutputEvents, -/// context = dyn Any, -/// error = Infallible, -/// ) -/// )] -/// struct Adapter; -/// ``` -/// -/// > __NOTE__: Single [`Adapter`] can [`Transformer::transform`][0] multiple -/// > [`Event`]s. -/// -/// ```rust -/// # #![feature(generic_associated_types)] -/// # -/// # use std::{any::Any, convert::Infallible}; -/// # -/// # use arcana::es::{event, Event, adapter::Transformer}; -/// # use derive_more::From; -/// # -/// # #[derive(event::Versioned)] -/// # #[event(name = "event", version = 1)] -/// # struct InputEvent; -/// # -/// # #[derive(event::Versioned)] -/// # #[event(name = "out", version = 1)] -/// # struct OutputEvents; -/// # -/// # #[derive(Event, From)] -/// # enum FirstInputEvents { -/// # Input(InputEvent), -/// # } -/// # -/// # #[derive(Event, From)] -/// # enum SecondInputEvents { -/// # Input(InputEvent), -/// # } -/// # -/// #[derive(Transformer)] -/// #[event( -/// transformer( -/// event(FirstInputEvents, SecondInputEvents), -/// transformed = OutputEvents, -/// context = dyn Any, -/// error = Infallible, -/// ) -/// )] -/// struct FirstAdapter; -/// -/// // equivalent to previous `derive` -/// #[derive(Transformer)] -/// #[event( -/// transformer( -/// event = FirstInputEvents, -/// transformed = OutputEvents, -/// context = dyn Any, -/// error = Infallible, -/// ), -/// transformer( -/// event = SecondInputEvents, -/// transformed = OutputEvents, -/// context = dyn Any, -/// error = Infallible, -/// ), -/// )] -/// struct SecondAdapter; -/// ``` -/// [0]: arcana_core::es::adapter::Transformer::transform() -/// [1]: arcana_core::es::adapter::Transformer::Transformed -/// [2]: arcana_core::es::adapter::Transformer::Context -/// [3]: arcana_core::es::adapter::Transformer::Error -/// [4]: arcana_codegen_impl::es::event::transformer::MAX_NUMBER_OF_VARIANTS -/// [`Adapter`]: arcana_core::es::Adapter -/// [`Event`]: trait@arcana_core::es::Event -/// [`Transformer`]: arcana_core::es::adapter::Transformer +/// TODO #[proc_macro_derive(EventTransformer, attributes(transformer))] pub fn derive_event_transformer(input: TokenStream) -> TokenStream { codegen::es::event::transformer::derive(input.into()) diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index e4faf3f..e720303 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -4,10 +4,11 @@ pub mod strategy; use futures::Stream; +use crate::es::event; + #[doc(inline)] pub use strategy::Strategy; -// TODO: describe specialization /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -60,388 +61,13 @@ pub trait Transformer { /// Instead of implementing [`Transformer`] manually, you can use this trait /// with some [`Strategy`]. -pub trait WithStrategy: Sized { +pub trait WithStrategy +where + Self: Sized, + Event: event::Versioned, +{ /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event type Strategy: Strategy; } - -/// Marker trait indicating that [`Event`] can be transformed by [`Adapter`]. -/// -/// [`Adapter`]: crate::es::Adapter -/// [`Event`]: crate::es::Event -pub trait TransformedBy {} - -impl TransformedBy for Ev where A: Transformer {} - -pub mod specialization { - //! [`Transformer`] machinery aiding codegen. - //! - //! This module describes traits used for - //! [auto-deref based specialization][1]. Basically we want to transform - //! [`Event`] using [`Transformer`] impl, if present. If not, fallback to - //! [`From`] impl, if present and, as last resort, entirely [`Skip`] it. - //! More detailed description of specialization levels provided below. - //! - //! # Specialization Order - //! - //! ## 1. [`TransformedBySkipAdapter`] - //! - //! If we are using [`Skip`] [`Strategy`] for transforming particular - //! [`Event`], this specialization allows to avoid implementation of - //! [`From`] [`Unknown`]. - //! - //! ## 2. [`TransformedByAdapter`] - //! - //! We prioritize custom [`Transformer`] implementations to allow custom - //! implementation of any [`Event`]. - //! - //! ## 3. [`TransformedByFrom`] - //! - //! If [`Event`] can trivially transformed into - //! [`Transformer::Transformed`][2] using [`From`] specialization, we do it. - //! - //! ## 4. [`TransformedByFromInitial`] - //! - //! If [`Event`] wrapped into [`event::Initial`] can trivially transformed - //! into [`Transformer::Transformed`][2] using [`From`] specialization, we - //! do it. - //! - //! ## 5. [`TransformedByFromUpcast`] - //! - //! TODO - //! - //! ## 6. [`TransformedByFromInitialUpcast`] - //! - //! TODO - //! - //! ## 7. [`TransformedByEmpty`] - //! - //! If none of the above applies, we just [`Skip`]. - //! - //! [1]: https://bit.ly/3nrTbtj - //! [2]: super::Transformer::Transformed - //! [`Event`]: event::Event - //! [`Skip`]: strategy::Skip - //! [`Strategy`]: strategy::Strategy - //! [`Unknown`]: strategy::Unknown - - #![allow(clippy::unused_self)] - - use std::marker::PhantomData; - - use crate::es::{ - adapter::{ - transformer::{strategy, WithStrategy}, - Transformer, - }, - event::{self, Upcast}, - }; - use futures::{future, stream, StreamExt as _}; - - /// Wrapper to satisfy [orphan rules][1]. - /// - /// [1]: https://bit.ly/2Xea7bG - #[derive(Debug)] - pub struct Wrap( - /// [`Adapter`] to transform [`Event`]. - /// - /// [`Adapter`]: crate::es::Adapter - /// [`Event`]: crate::es::Event - pub Adapter, - /// Transformed [`Event`]. - /// - /// [`Event`]: crate::es::Event - pub Event, - /// Bound for [`Transformer::Transformed`][1]. - /// - /// [1]: super::Transformer::Transformed - pub PhantomData, - ); - - // With Skip Adapter - - /// TODO - pub trait TransformedBySkipAdapter { - /// TODO - fn get_tag(&self) -> AdapterSkippedTag; - } - - impl TransformedBySkipAdapter - for &&&&&&Wrap<&Adapter, &Event, TransformedEvent> - where - Adapter: WithStrategy, - { - fn get_tag(&self) -> AdapterSkippedTag { - AdapterSkippedTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct AdapterSkippedTag; - - impl AdapterSkippedTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - _: Event, - _: &Ctx, - ) -> stream::Empty> - where - Ctx: ?Sized, - { - stream::empty() - } - } - - // With Adapter - - /// TODO - pub trait TransformedByAdapter { - /// TODO - fn get_tag(&self) -> AdapterTag; - } - - impl TransformedByAdapter - for &&&&&Wrap<&Adapter, &Event, TransformedEvent> - where - Adapter: Transformer, - { - fn get_tag(&self) -> AdapterTag { - AdapterTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct AdapterTag; - - impl AdapterTag { - /// TODO - pub fn transform_event<'me, 'ctx, 'out, Adapter, Ev, TrEv, Ctx, Err>( - self, - adapter: &'me Adapter, - ev: Ev, - context: &'ctx Ctx, - ) -> AdapterTagStream<'out, Adapter, Ev, TrEv, Err> - where - 'me: 'out, - 'ctx: 'out, - Ev: 'static, - Ctx: ?Sized, - Adapter: Transformer, - TrEv: From, - Err: From, - { - >::transform(adapter, ev, context) - .map(|res| res.map(Into::into).map_err(Into::into)) - } - } - - type AdapterTagStream<'out, Adapter, Event, TrEvent, Err> = stream::Map< - >::TransformedStream<'out>, - fn( - Result< - >::Transformed, - >::Error, - >, - ) -> Result, - >; - - // With From - - /// TODO - pub trait TransformedByFrom { - /// TODO - fn get_tag(&self) -> FromTag; - } - - impl TransformedByFrom - for &&&&Wrap<&Adapter, &Event, TransformedEvent> - where - TransformedEvent: From, - { - fn get_tag(&self) -> FromTag { - FromTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct FromTag; - - impl FromTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - ev: Event, - _: &Ctx, - ) -> stream::Once>> - where - Ctx: ?Sized, - TrEvent: From, - { - stream::once(future::ready(Ok(ev.into()))) - } - } - - // With From Initial - - /// TODO - pub trait TransformedByFromInitial { - /// TODO - fn get_tag(&self) -> FromInitialTag; - } - - impl TransformedByFromInitial - for &&&Wrap<&Adapter, &Event, TransformedEvent> - where - TransformedEvent: From>, - { - fn get_tag(&self) -> FromInitialTag { - FromInitialTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct FromInitialTag; - - impl FromInitialTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - ev: Event, - _: &Ctx, - ) -> stream::Once>> - where - Ctx: ?Sized, - TrEvent: From>, - { - stream::once(future::ready(Ok(event::Initial(ev).into()))) - } - } - - // With From Upcast - - /// TODO - pub trait TransformedByFromUpcast { - /// TODO - fn get_tag(&self) -> FromUpcastTag; - } - - impl TransformedByFromUpcast - for &Wrap<&Adapter, &Event, TransformedEvent> - where - Event: Upcast, - TransformedEvent: From, - { - fn get_tag(&self) -> FromUpcastTag { - FromUpcastTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct FromUpcastTag; - - impl FromUpcastTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - ev: Event, - _: &Ctx, - ) -> stream::Once>> - where - Ctx: ?Sized, - Event: Upcast, - TrEvent: From, - { - stream::once(future::ready(Ok(Event::Into::from(ev).into()))) - } - } - - // With From Initial Upcast - - /// TODO - pub trait TransformedByFromInitialUpcast { - /// TODO - fn get_tag(&self) -> FromInitialUpcastTag; - } - - impl TransformedByFromInitialUpcast - for &Wrap<&Adapter, &Event, TransformedEvent> - where - Event: Upcast, - TransformedEvent: From>, - { - fn get_tag(&self) -> FromInitialUpcastTag { - FromInitialUpcastTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct FromInitialUpcastTag; - - impl FromInitialUpcastTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - ev: Event, - _: &Ctx, - ) -> stream::Once>> - where - Ctx: ?Sized, - Event: Upcast, - TrEvent: From>, - { - stream::once(future::ready(Ok(event::Initial(Event::Into::from( - ev, - )) - .into()))) - } - } - - // Skip - - /// TODO - pub trait TransformedByEmpty { - /// TODO - fn get_tag(&self) -> EmptyTag; - } - - impl TransformedByEmpty - for Wrap<&Adapter, &Event, TransformedEvent> - { - fn get_tag(&self) -> EmptyTag { - EmptyTag - } - } - - /// TODO - #[derive(Clone, Copy, Debug)] - pub struct EmptyTag; - - impl EmptyTag { - /// TODO - pub fn transform_event( - self, - _: &Adapter, - _: Event, - _: &Ctx, - ) -> stream::Empty> - where - Ctx: ?Sized, - { - stream::empty() - } - } -} diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index 35dfefd..b5bc70a 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -11,8 +11,13 @@ use crate::es::{adapter, event}; use super::{Transformer, WithStrategy}; -/// Generalized [`Transformer`]. -pub trait Strategy { +/// Generalized [`Transformer`] for [`Versioned`] events. +/// +/// [`Versioned`]: event::Versioned +pub trait Strategy +where + Event: event::Versioned, +{ /// Context for converting [`Event`]s. /// /// [`Event`]: crate::es::Event @@ -92,6 +97,7 @@ pub struct Initialized(PhantomData); impl Strategy for Initialized where + Event: event::Versioned, InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, @@ -132,6 +138,7 @@ pub struct Skip; impl Strategy for Skip where + Event: event::Versioned, Adapter: adapter::WithError, Adapter::Transformed: 'static, Adapter::Error: 'static, @@ -161,7 +168,10 @@ where #[derive(Clone, Copy, Debug)] pub struct AsIs; -impl Strategy for AsIs { +impl Strategy for AsIs +where + Event: event::Versioned + 'static, +{ type Context = dyn Any; type Error = Infallible; type Transformed = Event; @@ -190,6 +200,7 @@ pub struct Into(PhantomData<(I, InnerStrategy)>); impl Strategy for Into where + Event: event::Versioned, InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, @@ -242,6 +253,7 @@ pub trait Splitter { impl Strategy for Split where + Event: event::Versioned, IntoEvent: 'static, Adapter: Splitter, Adapter::Iterator: 'static, diff --git a/examples/chat/src/event/chat.rs b/examples/chat/src/event/chat.rs index bdc8695..cc6cc94 100644 --- a/examples/chat/src/event/chat.rs +++ b/examples/chat/src/event/chat.rs @@ -22,14 +22,4 @@ pub mod v1 { #[derive(Debug, event::Versioned)] #[event(name = "chat.created", version = 1)] pub struct Created; - - impl From for super::private::Created { - fn from(_: Created) -> Self { - Self - } - } - - impl event::Upcast for Created { - type Into = super::private::Created; - } } diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index f7bac5e..657ffc4 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -6,15 +6,8 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{ - specialization, TransformedBy, Transformer, WithStrategy, -}; +pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; #[cfg(feature = "derive")] #[doc(inline)] pub use arcana_codegen::es::transformer::Transformer; -#[cfg(feature = "derive")] -#[doc(hidden)] -// TODO: Replace with panic once `const_panic` is stabilized. -// https://github.com/rust-lang/rust/issues/51999 -pub use arcana_codegen::sa::const_assert as too_many_variants_in_enum; diff --git a/src/es/event.rs b/src/es/event.rs index 5d46d28..bac97e7 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -2,8 +2,7 @@ #[doc(inline)] pub use arcana_core::es::event::{ - Event, Initial, Initialized, Name, Sourced, Sourcing, Upcast, Version, - Versioned, + Event, Initial, Initialized, Name, Sourced, Sourcing, Version, Versioned, }; #[cfg(feature = "derive")] From 6090db042638bf659fc2be71aa876a7e2f60cfef Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Sep 2021 14:44:52 +0300 Subject: [PATCH 062/104] WIP [skip ci] --- codegen/shim/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index cd6e402..3a0cf1f 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -35,13 +35,16 @@ use proc_macro::TokenStream; /// is that all the underlying [`Event`] or [`Versioned`] impls should be /// derived too. /// -/// Also, provides a blanket [`Sourced`] implementation for every state, which -/// can be sourced from all the enum variants. -/// /// > __WARNING:__ Currently may not work with complex generics using where /// > clause because of `const` evaluation limitations. Should be /// > lifted once [rust-lang/rust#57775] is resolved. /// +/// # Blanket implementations +/// +/// - [`Sourced`] for every state, which can be sourced from all enum variants; +/// - [`Transformer`] for every [`Adapter`], that can transform all enum +/// variants. +/// /// # Variant attributes /// /// #### `#[event(ignore)]` (optional) @@ -104,8 +107,10 @@ use proc_macro::TokenStream; /// } /// ``` /// +/// [`Adapter`]: arcana_core::es::Adapter /// [`Event`]: arcana_core::es::Event /// [`Sourced`]: arcana_core::es::event::Sourced +/// [`Transformer`]: arcana_core::es::adapter::Transformer /// [`Versioned`]: arcana_core::es::event::Versioned /// [0]: arcana_core::es::Event::name() /// [1]: arcana_core::es::Event::version() From 076ccf8e87e58865d0d65b39e44b121a4b630147 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Sep 2021 16:14:31 +0300 Subject: [PATCH 063/104] Try generic Context instead of associated type [skip ci] --- codegen/impl/src/es/event/mod.rs | 82 +++++++------- codegen/impl/src/es/event/transformer.rs | 20 +++- core/src/es/adapter/mod.rs | 117 ++++++++++---------- core/src/es/adapter/transformer/mod.rs | 14 +-- core/src/es/adapter/transformer/strategy.rs | 77 +++++++------ examples/chat/src/main.rs | 2 +- examples/chat/src/storage/chat.rs | 23 ++-- examples/chat/src/storage/email.rs | 27 ++--- examples/chat/src/storage/message.rs | 15 ++- 9 files changed, 189 insertions(+), 188 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 554248a..c69b4a7 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -325,22 +325,18 @@ impl Definition { let mut generics = self.generics.clone(); generics.params.push(parse_quote! { __A }); - generics - .make_where_clause() - .predicates - .push(parse_quote! { __A: ::arcana::es::adapter::WithError }); + generics.params.push(parse_quote! { __Ctx }); + generics.make_where_clause().predicates.push(parse_quote! { + __A: ::arcana::es::adapter::WithError<__Ctx> + }); generics.make_where_clause().predicates.push({ - let adapter_constrains = self.variants.iter() + let adapter_constrains = self + .variants + .iter() .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { - quote! { - ::arcana::es::adapter::Transformer< - #ty, - Context = <__A as ::arcana::es::adapter::WithError>:: - Context, - > - } - }); + quote! { ::arcana::es::adapter::Transformer<#ty, __Ctx> } + }); let self_bound = quote! { Self: #( #adapter_constrains )+* }; parse_quote! { #self_bound } }); @@ -352,13 +348,14 @@ impl Definition { .map(|ty| { quote! { ::std::convert::From< - >:: - Transformed + >::Transformed > } }); let transformed_bound = quote! { - <__A as ::arcana::es::adapter::WithError>::Transformed: + <__A as ::arcana::es::adapter::WithError<__Ctx>>::Transformed: #( #adapter_constrains )+* }; parse_quote! { #transformed_bound + 'static } @@ -371,13 +368,14 @@ impl Definition { .map(|ty| { quote! { ::std::convert::From< - >:: - Error + >::Error > } }); let err_bound = quote! { - <__A as ::arcana::es::adapter::WithError>::Error: + <__A as ::arcana::es::adapter::WithError<__Ctx>>::Error: #( #adapter_constrains )+* }; parse_quote! { #err_bound + 'static } @@ -389,8 +387,9 @@ impl Definition { .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { parse_quote! { - >::Error: - 'static + >::Error: 'static } }) { @@ -402,8 +401,9 @@ impl Definition { .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { parse_quote! { - >:: - Transformed: 'static + >::Transformed: 'static } }) { @@ -416,23 +416,21 @@ impl Definition { quote! { #[automatically_derived] impl#impl_gen ::arcana::es::adapter::Transformer< - #event#type_gen + #event#type_gen, __Ctx > for ::arcana::es::adapter::Wrapper<__A> #where_clause { - type Context = <__A as ::arcana::es::adapter::WithError>:: - Context; - type Error = <__A as ::arcana::es::adapter::WithError>::Error; - type Transformed = <__A as ::arcana::es::adapter::WithError>:: - Transformed; + type Error = <__A as ::arcana::es::adapter:: + WithError<__Ctx>>::Error; + type Transformed = <__A as ::arcana::es::adapter:: + WithError<__Ctx>>::Transformed; type TransformedStream<'out> = #transformed; fn transform<'me, 'ctx, 'out>( &'me self, __event: #event, - __context: &'ctx <__A as ::arcana::es::adapter::WithError>:: - Context, - ) -> >:: - TransformedStream<'out> + __context: &'ctx __Ctx, + ) -> >::TransformedStream<'out> where 'me: 'out, 'ctx: 'out, @@ -465,20 +463,21 @@ impl Definition { let transformed_stream = |event: &syn::Type| { quote! { ::arcana::es::adapter::codegen::futures::stream::Map< - >:: - TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, + Transformer<#event, __Ctx>>::Transformed, >::Error, + Transformer<#event, __Ctx>>::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<#from, __Ctx>>::Transformed, >::Error, + Transformer<#from, __Ctx>>::Error, > > } @@ -531,8 +530,9 @@ impl Definition { .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { ::arcana::es::adapter::codegen::futures::StreamExt::map( - >:: - transform(self, __event, __context), + >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { ::std::result::Result::map_err( diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index e6ff60b..cc4df77 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -6,6 +6,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, + parse_quote, punctuated::Punctuated, token, }; @@ -171,7 +172,22 @@ impl Definition { /// TODO #[must_use] pub fn impl_strategies(&self) -> TokenStream { - let (impl_gen, type_gen, where_cl) = self.generics.split_for_impl(); + let mut generics = self.generics.clone(); + generics.params.push(parse_quote! { __Ctx }); + generics.make_where_clause().predicates.push( + parse_quote! { Self: ::arcana::es::adapter::WithError<__Ctx> }, + ); + generics.make_where_clause().predicates.push(parse_quote! { + >::Transformed: 'static + }); + generics.make_where_clause().predicates.push(parse_quote! { + >::Error: 'static + }); + + let (impl_gen, _, where_cl) = generics.split_for_impl(); + let (_, type_gen, _) = self.generics.split_for_impl(); let adapter = &self.adapter; self.strategies @@ -182,7 +198,7 @@ impl Definition { .map(|(ev, strategy)| { quote! { impl#impl_gen ::arcana::es::adapter::transformer:: - WithStrategy<#ev> for #adapter#type_gen #where_cl + WithStrategy<#ev, __Ctx> for #adapter#type_gen #where_cl { type Strategy = #strategy; } diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index bc67c6c..1786bf8 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -15,29 +15,29 @@ use ref_cast::RefCast; #[doc(inline)] pub use self::transformer::Transformer; +/// TODO +pub trait WithError { + /// TODO + type Error; + + /// TODO + type Transformed; +} + /// TODO #[derive(Debug, RefCast)] #[repr(transparent)] pub struct Wrapper(pub A); -impl WithError for Wrapper { - type Context = A::Context; +impl WithError for Wrapper +where + A: WithError, + Ctx: ?Sized, +{ type Error = A::Error; type Transformed = A::Transformed; } -/// TODO -pub trait WithError { - /// TODO - type Context: ?Sized; - - /// TODO - type Error; - - /// TODO - type Transformed; -} - /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -52,12 +52,7 @@ pub trait WithError { /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Adapter { - /// Context for converting [`Event`]s. - /// - /// [`Event`]: crate::es::Event - type Context: ?Sized; - +pub trait Adapter { /// Error of this [`Adapter`]. type Error; @@ -81,33 +76,33 @@ pub trait Adapter { fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, 'ctx: 'out; } -impl Adapter for A +impl Adapter for A where Events: Stream + 'static, - A: WithError, - Wrapper: Transformer + 'static, - as Transformer>::Context: 'static, - ::Transformed: - From< as Transformer>::Transformed>, - ::Error: - From< as Transformer>::Error>, + Ctx: 'static + ?Sized, + A: WithError, + Wrapper: Transformer + 'static, + >::Transformed: + From< as Transformer>::Transformed>, + >::Error: + From< as Transformer>::Error>, { - type Context = as Transformer>::Context; - type Error = ::Error; - type Transformed = ::Transformed; - type TransformedStream<'out> = TransformedStream<'out, Wrapper, Events>; + type Error = >::Error; + type Transformed = >::Transformed; + type TransformedStream<'out> = + TransformedStream<'out, Wrapper, Events, Ctx>; fn transform_all<'me, 'ctx, 'out>( &'me self, events: Events, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -119,24 +114,27 @@ where #[pin_project] /// [`Stream`] for [`Adapter`] blanket impl. -pub struct TransformedStream<'out, Adapter, Events> +pub struct TransformedStream<'out, Adapter, Events, Ctx> where Events: Stream, - Adapter: Transformer, + Adapter: Transformer, + Ctx: ?Sized, { #[pin] events: Events, #[pin] - transformed_stream: AdapterTransformedStream<'out, Events::Item, Adapter>, + transformed_stream: + AdapterTransformedStream<'out, Events::Item, Adapter, Ctx>, adapter: &'out Adapter, - context: &'out Adapter::Context, + context: &'out Ctx, } -impl<'out, Adapter, Events> Debug for TransformedStream<'out, Adapter, Events> +impl<'out, Adapter, Events, Ctx> Debug + for TransformedStream<'out, Adapter, Events, Ctx> where Events: Debug + Stream, - Adapter: Debug + Transformer, - Adapter::Context: Debug, + Adapter: Debug + Transformer, + Ctx: Debug + ?Sized, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("TransformStream") @@ -147,26 +145,23 @@ where } } -type AdapterTransformedStream<'out, Event, Adapter> = future::Either< - >::TransformedStream<'out>, +type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< + >::TransformedStream<'out>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; -impl<'out, Adapter, Events> TransformedStream<'out, Adapter, Events> +impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> where Events: Stream, - Adapter: Transformer, + Adapter: Transformer, + Ctx: ?Sized, { - fn new( - adapter: &'out Adapter, - events: Events, - context: &'out Adapter::Context, - ) -> Self { + fn new(adapter: &'out Adapter, events: Events, context: &'out Ctx) -> Self { Self { events, transformed_stream: stream::empty().right_stream(), @@ -176,18 +171,20 @@ where } } -impl<'out, Adapter, Events> Stream for TransformedStream<'out, Adapter, Events> +impl<'out, Adapter, Events, Ctx> Stream + for TransformedStream<'out, Adapter, Events, Ctx> where Events: Stream, - Adapter: Transformer + WithError, - ::Transformed: - From<>::Transformed>, - ::Error: - From<>::Error>, + Ctx: ?Sized, + Adapter: Transformer + WithError, + >::Transformed: + From<>::Transformed>, + >::Error: + From<>::Error>, { type Item = Result< - ::Transformed, - ::Error, + >::Transformed, + >::Error, >; fn poll_next( diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index e720303..82d8cbf 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -23,12 +23,7 @@ pub use strategy::Strategy; /// [`Skip`]: strategy::Skip /// [`Split`]: strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Transformer { - /// Context for converting [`Event`]s. - /// - /// [`Event`]: crate::es::Event - type Context: ?Sized; - +pub trait Transformer { /// Error of this [`Transformer`]. type Error; @@ -52,7 +47,7 @@ pub trait Transformer { fn transform<'me, 'ctx, 'out>( &'me self, event: Event, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -61,13 +56,14 @@ pub trait Transformer { /// Instead of implementing [`Transformer`] manually, you can use this trait /// with some [`Strategy`]. -pub trait WithStrategy +pub trait WithStrategy where Self: Sized, Event: event::Versioned, + Ctx: ?Sized, { /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event - type Strategy: Strategy; + type Strategy: Strategy; } diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index b5bc70a..53fab34 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -1,8 +1,7 @@ //! [`Strategy`] definition and default implementations. use std::{ - any::Any, convert::Infallible, fmt::Debug, iter::Iterator, - marker::PhantomData, + convert::Infallible, fmt::Debug, iter::Iterator, marker::PhantomData, }; use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; @@ -14,15 +13,11 @@ use super::{Transformer, WithStrategy}; /// Generalized [`Transformer`] for [`Versioned`] events. /// /// [`Versioned`]: event::Versioned -pub trait Strategy +pub trait Strategy where Event: event::Versioned, + Ctx: ?Sized, { - /// Context for converting [`Event`]s. - /// - /// [`Event`]: crate::es::Event - type Context: ?Sized; - /// Error of this [`Strategy`]. type Error; @@ -46,42 +41,43 @@ where fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, 'ctx: 'out; } -impl Transformer for adapter::Wrapper +impl Transformer for adapter::Wrapper where + Ctx: ?Sized, Event: event::Versioned, - Adapter: WithStrategy + adapter::WithError, - Adapter::Strategy: Strategy, - ::Transformed: - From<>::Transformed>, - ::Error: - From<>::Error>, + Adapter: WithStrategy + adapter::WithError, + Adapter::Strategy: Strategy, + >::Transformed: + From<>::Transformed>, + >::Error: + From<>::Error>, { - type Context = >::Context; - type Error = >::Error; + type Error = >::Error; type Transformed = - >::Transformed; + >::Transformed; type TransformedStream<'out> = >::TransformedStream<'out>; fn transform<'me, 'ctx, 'out>( &'me self, event: Event, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, 'ctx: 'out, { - >::transform( + >::transform( &self.0, event, context, ) } @@ -94,15 +90,15 @@ where #[derive(Clone, Debug)] pub struct Initialized(PhantomData); -impl Strategy +impl Strategy for Initialized where + Ctx: ?Sized, Event: event::Versioned, - InnerStrategy: Strategy, + InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, { - type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = event::Initial; type TransformedStream<'out> = stream::MapOk< @@ -113,7 +109,7 @@ where fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, - context: &'ctx Self::Context, + context: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -136,14 +132,14 @@ type WrapInitial = fn(Event) -> event::Initial; #[derive(Clone, Copy, Debug)] pub struct Skip; -impl Strategy for Skip +impl Strategy for Skip where + Ctx: ?Sized, Event: event::Versioned, - Adapter: adapter::WithError, + Adapter: adapter::WithError, Adapter::Transformed: 'static, Adapter::Error: 'static, { - type Context = dyn Any; type Error = Adapter::Error; type Transformed = Adapter::Transformed; type TransformedStream<'out> = @@ -152,7 +148,7 @@ where fn transform<'me, 'ctx, 'out>( _: &'me Adapter, _: Event, - _: &'ctx Self::Context, + _: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -168,11 +164,11 @@ where #[derive(Clone, Copy, Debug)] pub struct AsIs; -impl Strategy for AsIs +impl Strategy for AsIs where + Ctx: ?Sized, Event: event::Versioned + 'static, { - type Context = dyn Any; type Error = Infallible; type Transformed = Event; type TransformedStream<'out> = @@ -181,7 +177,7 @@ where fn transform<'me, 'ctx, 'out>( _: &'me Adapter, event: Event, - _: &'ctx Self::Context, + _: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -197,16 +193,16 @@ where #[derive(Copy, Clone, Debug)] pub struct Into(PhantomData<(I, InnerStrategy)>); -impl Strategy - for Into +impl + Strategy for Into where + Ctx: ?Sized, Event: event::Versioned, - InnerStrategy: Strategy, + InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, IntoEvent: From + 'static, { - type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; type TransformedStream<'out> = stream::MapOk< @@ -217,7 +213,7 @@ where fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, - ctx: &'ctx Self::Context, + ctx: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -251,14 +247,15 @@ pub trait Splitter { fn split(&self, event: From) -> Self::Iterator; } -impl Strategy for Split +impl Strategy + for Split where + Ctx: ?Sized, Event: event::Versioned, IntoEvent: 'static, Adapter: Splitter, Adapter::Iterator: 'static, { - type Context = dyn Any; type Error = Infallible; type Transformed = ::Item; type TransformedStream<'out> = SplitStream; @@ -266,7 +263,7 @@ where fn transform<'me, 'ctx, 'out>( adapter: &'me Adapter, event: Event, - _: &'ctx Self::Context, + _: &'ctx Ctx, ) -> Self::TransformedStream<'out> where 'me: 'out, diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 0e61fa3..93a08cf 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -17,7 +17,7 @@ async fn main() { let mut email = Option::::None; let chat_events = storage::chat::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &1) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index b6fb594..3418d2e 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,39 +1,36 @@ -use std::{any::Any, convert::Infallible}; +use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::{self, strategy}, + transformer::strategy::{AsIs, Initialized, Into, Skip}, Transformer, }; use crate::event; -impl adapter::WithError for Adapter { - type Context = dyn Any; +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Chat; } #[derive(Debug, Transformer)] #[transformer( - strategy::Initialized => ( + Initialized => ( event::chat::public::Created, event::chat::private::Created, ), - strategy::AsIs => event::message::Posted, - strategy::Skip => ( + AsIs => event::message::Posted, + Skip => ( event::email::v1::AddedAndConfirmed, event::email::Confirmed, event::email::Added, - ) + ), + Initialized> => ( + event::chat::v1::Created, + ), )] pub struct Adapter; -impl transformer::WithStrategy for Adapter { - type Strategy = - strategy::Initialized>; -} - // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index da66b2f..90a1895 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,9 +1,9 @@ -use std::{any::Any, array, convert::Infallible}; +use std::{array, convert::Infallible}; use arcana::es::{ adapter::{ self, - transformer::{self, strategy}, + transformer::strategy::{AsIs, Initialized, Skip, Split, Splitter}, Transformer, }, event::Initial, @@ -12,34 +12,29 @@ use either::Either; use crate::event; -impl adapter::WithError for Adapter { - type Context = dyn Any; +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Email; } #[derive(Debug, Transformer)] #[transformer( - strategy::Initialized => event::email::Added, - strategy::AsIs => event::email::Confirmed, - strategy::Skip => ( + Initialized => event::email::Added, + AsIs => event::email::Confirmed, + Skip => ( event::chat::public::Created, event::chat::private::Created, event::chat::v1::Created, event::message::Posted, - ) + ), + Split> => ( + event::email::v1::AddedAndConfirmed, + ), )] pub struct Adapter; -impl transformer::WithStrategy - for Adapter -{ - type Strategy = - strategy::Split>; -} - impl - strategy::Splitter< + Splitter< event::email::v1::AddedAndConfirmed, Either, > for Adapter diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index c5a2c52..bce2a66 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,21 +1,24 @@ -use std::{any::Any, convert::Infallible}; +use std::convert::Infallible; -use arcana::es::adapter::{self, transformer::strategy, Transformer}; +use arcana::es::adapter::{ + self, + transformer::strategy::{Initialized, Skip}, + Transformer, +}; use crate::event; -impl adapter::WithError for Adapter { - type Context = dyn Any; +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Message; } #[derive(Debug, Transformer)] #[transformer( - strategy::Initialized => ( + Initialized => ( event::message::Posted, ), - strategy::Skip => ( + Skip => ( event::chat::public::Created, event::chat::private::Created, event::chat::v1::Created, From ad7a03afb3db4b8309c3b23f6645f011ad6b04f7 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 10:04:52 +0300 Subject: [PATCH 064/104] Bootstrap Context with GATs [skip ci] --- codegen/impl/src/es/event/mod.rs | 7 +- core/src/es/adapter/mod.rs | 102 ++++---- core/src/es/adapter/transformer/mod.rs | 28 ++- core/src/es/adapter/transformer/strategy.rs | 167 +++++++++----- examples/chat/src/main.rs | 2 +- examples/chat/src/storage/chat.rs | 71 ++++-- examples/chat/src/storage/email.rs | 66 ++++-- examples/chat/src/storage/message.rs | 66 ++++-- examples/chat/src/storage/mod.rs | 243 +++++++++++++++++++- src/es/adapter/mod.rs | 8 +- 10 files changed, 580 insertions(+), 180 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index c69b4a7..4a3809b 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -36,12 +36,7 @@ pub struct VariantAttrs { /// /// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append( - impl_event, - impl_event_sourced, - gen_uniqueness_glue_code, - impl_transformer -))] +#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 1786bf8..2633845 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -3,7 +3,7 @@ pub mod transformer; use std::{ - fmt::{Debug, Formatter}, + fmt, pin::Pin, task::{Context, Poll}, }; @@ -16,7 +16,7 @@ use ref_cast::RefCast; pub use self::transformer::Transformer; /// TODO -pub trait WithError { +pub trait WithError { /// TODO type Error; @@ -29,15 +29,17 @@ pub trait WithError { #[repr(transparent)] pub struct Wrapper(pub A); -impl WithError for Wrapper +impl WithError for Wrapper where - A: WithError, - Ctx: ?Sized, + A: WithError, { type Error = A::Error; type Transformed = A::Transformed; } +/// TODO +pub trait Correct {} + /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -52,7 +54,7 @@ where /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Adapter { +pub trait Adapter { /// Error of this [`Adapter`]. type Error; @@ -65,48 +67,52 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out, Ctx: 'static>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me, 'ctx, 'out>( + fn transform_all<'me, 'ctx, 'out, Context>( &'me self, events: Events, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + context: &'ctx Context, + ) -> Self::TransformedStream<'out, Context> where 'me: 'out, - 'ctx: 'out; + 'ctx: 'out, + Context: 'static; } -impl Adapter for A +impl Adapter for A where Events: Stream + 'static, - Ctx: 'static + ?Sized, - A: WithError, - Wrapper: Transformer + 'static, - >::Transformed: - From< as Transformer>::Transformed>, - >::Error: - From< as Transformer>::Error>, + A: WithError, + Wrapper: Transformer + 'static, + ::Transformed: + From< as Transformer>::Transformed>, + ::Error: + From< as Transformer>::Error>, { - type Error = >::Error; - type Transformed = >::Transformed; - type TransformedStream<'out> = + type Error = ::Error; + type Transformed = ::Transformed; + type TransformedStream<'out, Ctx: 'static> = TransformedStream<'out, Wrapper, Events, Ctx>; - fn transform_all<'me, 'ctx, 'out>( + fn transform_all<'me, 'ctx, 'out, Ctx>( &'me self, events: Events, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { TransformedStream::new(RefCast::ref_cast(self), events, context) } @@ -116,9 +122,10 @@ where /// [`Stream`] for [`Adapter`] blanket impl. pub struct TransformedStream<'out, Adapter, Events, Ctx> where + Ctx: 'static, Events: Stream, - Adapter: Transformer, - Ctx: ?Sized, + Adapter: Transformer, + >::Context: Correct, { #[pin] events: Events, @@ -129,14 +136,15 @@ where context: &'out Ctx, } -impl<'out, Adapter, Events, Ctx> Debug +impl<'out, Adapter, Events, Ctx> fmt::Debug for TransformedStream<'out, Adapter, Events, Ctx> where - Events: Debug + Stream, - Adapter: Debug + Transformer, - Ctx: Debug + ?Sized, + Ctx: fmt::Debug + 'static, + Events: fmt::Debug + Stream, + Adapter: fmt::Debug + Transformer, + >::Context: Correct, { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TransformStream") .field("events", &self.events) .field("adapter", &self.adapter) @@ -146,20 +154,21 @@ where } type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< - >::TransformedStream<'out>, + >::TransformedStream<'out, Ctx>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> where + Ctx: 'static, Events: Stream, - Adapter: Transformer, - Ctx: ?Sized, + Adapter: Transformer, + >::Context: Correct, { fn new(adapter: &'out Adapter, events: Events, context: &'out Ctx) -> Self { Self { @@ -174,17 +183,18 @@ where impl<'out, Adapter, Events, Ctx> Stream for TransformedStream<'out, Adapter, Events, Ctx> where + Ctx: 'static, Events: Stream, - Ctx: ?Sized, - Adapter: Transformer + WithError, - >::Transformed: - From<>::Transformed>, - >::Error: - From<>::Error>, + Adapter: Transformer + WithError, + >::Context: Correct, + ::Transformed: + From<>::Transformed>, + ::Error: + From<>::Error>, { type Item = Result< - >::Transformed, - >::Error, + ::Transformed, + ::Error, >; fn poll_next( diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 82d8cbf..ca38a01 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -4,7 +4,7 @@ pub mod strategy; use futures::Stream; -use crate::es::event; +use crate::es::{adapter::Correct, event}; #[doc(inline)] pub use strategy::Strategy; @@ -23,7 +23,10 @@ pub use strategy::Strategy; /// [`Skip`]: strategy::Skip /// [`Split`]: strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Transformer { +pub trait Transformer { + /// TODO + type Context: Correct; + /// Error of this [`Transformer`]. type Error; @@ -36,34 +39,37 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out, Ctx: 'static>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( &'me self, event: Event, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, - 'ctx: 'out; + 'ctx: 'out, + Ctx: 'static; } /// Instead of implementing [`Transformer`] manually, you can use this trait /// with some [`Strategy`]. -pub trait WithStrategy +pub trait WithStrategy where Self: Sized, Event: event::Versioned, - Ctx: ?Sized, { /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event - type Strategy: Strategy; + type Strategy: Strategy; } diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index 53fab34..cc04701 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -6,18 +6,23 @@ use std::{ use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; -use crate::es::{adapter, event}; +use crate::es::{ + adapter::{self, Correct}, + event, +}; use super::{Transformer, WithStrategy}; /// Generalized [`Transformer`] for [`Versioned`] events. /// /// [`Versioned`]: event::Versioned -pub trait Strategy +pub trait Strategy where Event: event::Versioned, - Ctx: ?Sized, { + /// TODO + type Context: Correct; + /// Error of this [`Strategy`]. type Error; @@ -30,54 +35,59 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out, Ctx: 'static>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( adapter: &'me Adapter, event: Event, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, - 'ctx: 'out; + 'ctx: 'out, + Ctx: 'static; } -impl Transformer for adapter::Wrapper +impl Transformer for adapter::Wrapper where - Ctx: ?Sized, Event: event::Versioned, - Adapter: WithStrategy + adapter::WithError, - Adapter::Strategy: Strategy, - >::Transformed: - From<>::Transformed>, - >::Error: - From<>::Error>, + Adapter: WithStrategy, + Adapter::Strategy: Strategy, { - type Error = >::Error; + type Context = + >::Context; + + type Error = >::Error; + type Transformed = - >::Transformed; - type TransformedStream<'out> = >::TransformedStream<'out>; - - fn transform<'me, 'ctx, 'out>( + >::Transformed; + + type TransformedStream<'out, Ctx: 'static> = + >::TransformedStream< + 'out, + Ctx, + >; + + fn transform<'me, 'ctx, 'out, Ctx>( &'me self, event: Event, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { - >::transform( + >::transform( &self.0, event, context, ) } @@ -90,30 +100,34 @@ where #[derive(Clone, Debug)] pub struct Initialized(PhantomData); -impl Strategy +impl Strategy for Initialized where - Ctx: ?Sized, Event: event::Versioned, - InnerStrategy: Strategy, + InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, { + type Context = InnerStrategy::Context; + type Error = InnerStrategy::Error; + type Transformed = event::Initial; - type TransformedStream<'out> = stream::MapOk< - InnerStrategy::TransformedStream<'out>, + + type TransformedStream<'out, Ctx: 'static> = stream::MapOk< + InnerStrategy::TransformedStream<'out, Ctx>, WrapInitial, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( adapter: &'me Adapter, event: Event, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) } @@ -132,27 +146,28 @@ type WrapInitial = fn(Event) -> event::Initial; #[derive(Clone, Copy, Debug)] pub struct Skip; -impl Strategy for Skip +impl Strategy for Skip where - Ctx: ?Sized, Event: event::Versioned, - Adapter: adapter::WithError, + Adapter: adapter::WithError, Adapter::Transformed: 'static, Adapter::Error: 'static, { + type Context = Any; type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out> = + type TransformedStream<'out, Ctx: 'static> = stream::Empty>; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( _: &'me Adapter, _: Event, _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { stream::empty() } @@ -164,24 +179,25 @@ where #[derive(Clone, Copy, Debug)] pub struct AsIs; -impl Strategy for AsIs +impl Strategy for AsIs where - Ctx: ?Sized, Event: event::Versioned + 'static, { + type Context = Any; type Error = Infallible; type Transformed = Event; - type TransformedStream<'out> = + type TransformedStream<'out, Ctx: 'static> = stream::Once>>; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( _: &'me Adapter, event: Event, _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { stream::once(future::ready(Ok(event))) } @@ -193,31 +209,32 @@ where #[derive(Copy, Clone, Debug)] pub struct Into(PhantomData<(I, InnerStrategy)>); -impl - Strategy for Into +impl Strategy + for Into where - Ctx: ?Sized, Event: event::Versioned, - InnerStrategy: Strategy, + InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, IntoEvent: From + 'static, { + type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'out> = stream::MapOk< - InnerStrategy::TransformedStream<'out>, + type TransformedStream<'out, Ctx: 'static> = stream::MapOk< + InnerStrategy::TransformedStream<'out, Ctx>, IntoFn, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( adapter: &'me Adapter, event: Event, ctx: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } @@ -247,27 +264,28 @@ pub trait Splitter { fn split(&self, event: From) -> Self::Iterator; } -impl Strategy - for Split +impl Strategy for Split where - Ctx: ?Sized, Event: event::Versioned, IntoEvent: 'static, Adapter: Splitter, Adapter::Iterator: 'static, { + type Context = Any; type Error = Infallible; type Transformed = ::Item; - type TransformedStream<'out> = SplitStream; + type TransformedStream<'out, Ctx: 'static> = + SplitStream; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, Ctx>( adapter: &'me Adapter, event: Event, _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> + ) -> Self::TransformedStream<'out, Ctx> where 'me: 'out, 'ctx: 'out, + Ctx: 'static, { stream::iter(adapter.split(event)).map(Ok) } @@ -282,3 +300,32 @@ type SplitStream = stream::Map< Infallible, >, >; + +/// TODO +#[derive(Debug)] +pub struct Any(T); + +impl Correct for Any {} + +/// TODO +#[derive(Debug)] +pub struct And(L, R); + +impl Correct for And +where + L: Correct, + R: Correct, +{ +} + +/// TODO +#[macro_export] +macro_rules! and { + ($head: ty $(,)?) => { $head }; + ($head: ty, $($tail: ty),* $(,)?) => { + $crate::es::adapter::transformer::strategy::And< + $head, + $crate::and!($($tail),*) + > + }; +} diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 93a08cf..0e61fa3 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -17,7 +17,7 @@ async fn main() { let mut email = Option::::None; let chat_events = storage::chat::Adapter - .transform_all(incoming_events(), &1) + .transform_all(incoming_events(), &()) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 3418d2e..6a44db7 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -2,35 +2,68 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::strategy::{AsIs, Initialized, Into, Skip}, - Transformer, + transformer::{ + self, + strategy::{AsIs, Initialized, Into, Skip}, + }, }; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Chat; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => ( - event::chat::public::Created, - event::chat::private::Created, - ), - AsIs => event::message::Posted, - Skip => ( - event::email::v1::AddedAndConfirmed, - event::email::Confirmed, - event::email::Added, - ), - Initialized> => ( - event::chat::v1::Created, - ), -)] +// #[derive(Debug, Transformer)] +// #[transformer( +// Initialized => ( +// event::chat::public::Created, +// event::chat::private::Created, +// ), +// AsIs => event::message::Posted, +// Skip => ( +// event::email::v1::AddedAndConfirmed, +// event::email::Confirmed, +// event::email::Added, +// ), +// Initialized> => ( +// event::chat::v1::Created, +// ), +// )] +#[derive(Debug)] pub struct Adapter; +impl transformer::WithStrategy for Adapter { + type Strategy = Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = AsIs; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Initialized>; +} + // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 90a1895..b37794a 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -3,8 +3,10 @@ use std::{array, convert::Infallible}; use arcana::es::{ adapter::{ self, - transformer::strategy::{AsIs, Initialized, Skip, Split, Splitter}, - Transformer, + transformer::{ + self, + strategy::{AsIs, Initialized, Skip, Split, Splitter}, + }, }, event::Initial, }; @@ -12,27 +14,57 @@ use either::Either; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Email; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => event::email::Added, - AsIs => event::email::Confirmed, - Skip => ( - event::chat::public::Created, - event::chat::private::Created, - event::chat::v1::Created, - event::message::Posted, - ), - Split> => ( - event::email::v1::AddedAndConfirmed, - ), -)] +// #[derive(Debug, Transformer)] +// #[transformer( +// Initialized => event::email::Added, +// AsIs => event::email::Confirmed, +// Skip => ( +// event::chat::public::Created, +// event::chat::private::Created, +// event::chat::v1::Created, +// event::message::Posted, +// ), +// Split> => ( +// event::email::v1::AddedAndConfirmed, +// ), +// )] pub struct Adapter; +impl transformer::WithStrategy for Adapter { + type Strategy = Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = AsIs; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = Split>; +} + impl Splitter< event::email::v1::AddedAndConfirmed, diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index bce2a66..973b6c6 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -2,29 +2,61 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::strategy::{Initialized, Skip}, - Transformer, + transformer::{ + self, + strategy::{Initialized, Skip}, + }, }; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Message; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => ( - event::message::Posted, - ), - Skip => ( - event::chat::public::Created, - event::chat::private::Created, - event::chat::v1::Created, - event::email::Added, - event::email::Confirmed, - event::email::v1::AddedAndConfirmed, - ), -)] +// #[derive(Debug, Transformer)] +// #[transformer( +// Initialized => ( +// event::message::Posted, +// ), +// Skip => ( +// event::chat::public::Created, +// event::chat::private::Created, +// event::chat::v1::Created, +// event::email::Added, +// event::email::Confirmed, +// event::email::v1::AddedAndConfirmed, +// ), +// )] pub struct Adapter; + +impl transformer::WithStrategy for Adapter { + type Strategy = Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = Skip; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = Skip; +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 6752beb..425817d 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -2,8 +2,12 @@ pub mod chat; pub mod email; pub mod message; -use arcana::es; +use arcana::{ + es, + es::adapter::{self, transformer::Transformer, And}, +}; use derive_more::From; +use futures::stream::{LocalBoxStream, StreamExt as _}; use crate::event; @@ -14,6 +18,72 @@ pub enum Event { Email(EmailEvent), } +impl Transformer for adapter::Wrapper + where + A: adapter::WithError, + Self: Transformer + + Transformer + + Transformer, + A::Error: From<>::Error> + + From<>::Error> + + From<>::Error> + + 'static, + A::Transformed: From<>::Transformed> + + From<>::Transformed> + + From<>::Transformed> + + 'static, +{ + type Context = And< + >::Context, + And< + >::Context, + >::Context< + Impl, + >, + >, + >; + + type Error = A::Error; + + type Transformed = A::Transformed; + + type TransformedStream<'out, Ctx: 'static> = + LocalBoxStream<'out, Result>; + + fn transform<'me, 'ctx, 'out, Ctx>( + &'me self, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out, Ctx> + where + 'me: 'out, + 'ctx: 'out, + Ctx: 'static, + { + match event { + Event::Chat(ev) => { + Transformer::::transform(self, ev, context) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + Event::Message(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + Event::Email(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + } + } +} + #[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), @@ -21,14 +91,185 @@ pub enum ChatEvent { PrivateCreated(event::chat::private::Created), } +impl Transformer for adapter::Wrapper + where + A: adapter::WithError, + Self: Transformer + + Transformer + + Transformer, + A::Error: From<>::Error> + + From<>::Error> + + From<>::Error> + + 'static, + A::Transformed: From<>::Transformed> + + From<>::Transformed> + + From<>::Transformed> + + 'static, +{ + type Context = And< + >::Context, + And< + >::Context, + >::Context< + Impl, + >, + >, + >; + + type Error = A::Error; + + type Transformed = A::Transformed; + + type TransformedStream<'out, Ctx: 'static> = + LocalBoxStream<'out, Result>; + + fn transform<'me, 'ctx, 'out, Ctx>( + &'me self, + event: ChatEvent, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out, Ctx> + where + 'me: 'out, + 'ctx: 'out, + Ctx: 'static, + { + match event { + ChatEvent::Created(ev) => { + Transformer::::transform(self, ev, context) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + ChatEvent::PublicCreated(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + ChatEvent::PrivateCreated(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + } + } +} + #[derive(Debug, es::Event, From)] pub enum MessageEvent { Posted(event::message::Posted), } +impl Transformer for adapter::Wrapper + where + A: adapter::WithError, + Self: Transformer, + A::Error: From<>::Error> + + 'static, + A::Transformed: From<>::Transformed> + + 'static, +{ + type Context = + >::Context; + + type Error = A::Error; + + type Transformed = A::Transformed; + + type TransformedStream<'out, Ctx: 'static> = + LocalBoxStream<'out, Result>; + + fn transform<'me, 'ctx, 'out, Ctx>( + &'me self, + event: MessageEvent, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out, Ctx> + where + 'me: 'out, + 'ctx: 'out, + Ctx: 'static, + { + match event { + MessageEvent::Posted(ev) => { + Transformer::::transform(self, ev, context) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + } + } +} + #[derive(Debug, es::Event, From)] pub enum EmailEvent { Added(event::email::Added), Confirmed(event::email::Confirmed), AddedAndConfirmed(event::email::v1::AddedAndConfirmed), } + +impl Transformer for adapter::Wrapper +where + A: adapter::WithError, + Self: Transformer + + Transformer + + Transformer, + A::Error: From<>::Error> + + From<>::Error> + + From<>::Error> + + 'static, + A::Transformed: From<>::Transformed> + + From<>::Transformed> + + From<>::Transformed> + + 'static, +{ + type Context = And< + >::Context, + And< + >::Context, + >::Context< + Impl, + >, + >, + >; + + type Error = A::Error; + + type Transformed = A::Transformed; + + type TransformedStream<'out, Ctx: 'static> = + LocalBoxStream<'out, Result>; + + fn transform<'me, 'ctx, 'out, Ctx>( + &'me self, + event: EmailEvent, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out, Ctx> + where + 'me: 'out, + 'ctx: 'out, + Ctx: 'static, + { + match event { + EmailEvent::Added(ev) => { + Transformer::::transform(self, ev, context) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + EmailEvent::Confirmed(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + EmailEvent::AddedAndConfirmed(ev) => { + Transformer::::transform( + self, ev, context, + ) + .map(|res| res.map(Into::into).map_err(Into::into)) + .boxed_local() + } + } + } +} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index c9616f2..105270a 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -6,8 +6,12 @@ pub mod transformer; pub use self::transformer::Transformer; #[doc(inline)] -pub use arcana_core::es::adapter::{ - Adapter, TransformedStream, WithError, Wrapper, +pub use arcana_core::{ + and, + es::adapter::{ + transformer::strategy::{And, Any}, + Adapter, Correct, WithError, Wrapper, + }, }; #[cfg(feature = "derive")] From c35eb205e21997bc9712ea37386f518a7b210443 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 10:41:37 +0300 Subject: [PATCH 065/104] Generate Transformer impl with Event derive macro [skip ci] --- codegen/impl/src/es/event/mod.rs | 101 +++++++------ examples/chat/src/storage/mod.rs | 243 +------------------------------ 2 files changed, 61 insertions(+), 283 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 4a3809b..f2b2c2f 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -36,7 +36,12 @@ pub struct VariantAttrs { /// /// [`Event`]: arcana_core::es::event::Event #[derive(Debug, ToTokens)] -#[to_tokens(append(impl_event, impl_event_sourced, gen_uniqueness_glue_code))] +#[to_tokens(append( + impl_event, + impl_event_sourced, + gen_uniqueness_glue_code, + impl_transformer, +))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. pub ident: syn::Ident, @@ -317,12 +322,12 @@ impl Definition { let inner_match = self.inner_match(); let transformed = self.transformed_stream(); + let context_bound = self.context_bound(); let mut generics = self.generics.clone(); generics.params.push(parse_quote! { __A }); - generics.params.push(parse_quote! { __Ctx }); generics.make_where_clause().predicates.push(parse_quote! { - __A: ::arcana::es::adapter::WithError<__Ctx> + __A: ::arcana::es::adapter::WithError }); generics.make_where_clause().predicates.push({ let adapter_constrains = self @@ -330,7 +335,7 @@ impl Definition { .iter() .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { - quote! { ::arcana::es::adapter::Transformer<#ty, __Ctx> } + quote! { ::arcana::es::adapter::Transformer<#ty> } }); let self_bound = quote! { Self: #( #adapter_constrains )+* }; parse_quote! { #self_bound } @@ -343,14 +348,13 @@ impl Definition { .map(|ty| { quote! { ::std::convert::From< - >::Transformed + >:: + Transformed > } }); let transformed_bound = quote! { - <__A as ::arcana::es::adapter::WithError<__Ctx>>::Transformed: + <__A as ::arcana::es::adapter::WithError>::Transformed: #( #adapter_constrains )+* }; parse_quote! { #transformed_bound + 'static } @@ -363,14 +367,13 @@ impl Definition { .map(|ty| { quote! { ::std::convert::From< - >::Error + >:: + Error > } }); let err_bound = quote! { - <__A as ::arcana::es::adapter::WithError<__Ctx>>::Error: + <__A as ::arcana::es::adapter::WithError>::Error: #( #adapter_constrains )+* }; parse_quote! { #err_bound + 'static } @@ -382,9 +385,8 @@ impl Definition { .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { parse_quote! { - >::Error: 'static + >:: + Error: 'static } }) { @@ -396,9 +398,8 @@ impl Definition { .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) .map(|ty| { parse_quote! { - >::Transformed: 'static + >:: + Transformed: 'static } }) { @@ -410,25 +411,25 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gen ::arcana::es::adapter::Transformer< - #event#type_gen, __Ctx - > for ::arcana::es::adapter::Wrapper<__A> #where_clause + impl#impl_gen ::arcana::es::adapter::Transformer<#event#type_gen> + for ::arcana::es::adapter::Wrapper<__A> #where_clause { - type Error = <__A as ::arcana::es::adapter:: - WithError<__Ctx>>::Error; - type Transformed = <__A as ::arcana::es::adapter:: - WithError<__Ctx>>::Transformed; - type TransformedStream<'out> = #transformed; + type Context<__Impl> = #context_bound; + type Error = <__A as ::arcana::es::adapter::WithError>::Error; + type Transformed = + <__A as ::arcana::es::adapter::WithError>::Transformed; + type TransformedStream<'out, __Ctx: 'static> = #transformed; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'ctx, 'out, __Ctx>( &'me self, - __event: #event, + __event: #event#type_gen, __context: &'ctx __Ctx, ) -> >::TransformedStream<'out> + Transformer<#event>>::TransformedStream<'out, __Ctx> where 'me: 'out, 'ctx: 'out, + __Ctx: 'static, { match __event { #inner_match @@ -453,26 +454,25 @@ impl Definition { /// [`stream::Map`]: futures::stream::Map #[must_use] pub fn transformed_stream(&self) -> TokenStream { - let from = &self.ident; + let event = &self.ident; - let transformed_stream = |event: &syn::Type| { + let transformed_stream = |from: &syn::Type| { quote! { ::arcana::es::adapter::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >:: + TransformedStream<'out, __Ctx>, fn( ::std::result::Result< >::Transformed, + Transformer<#from >>::Transformed, >::Error, + Transformer<#from >>::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<#event >>::Transformed, >::Error, + Transformer<#event >>::Error, > > } @@ -525,9 +525,8 @@ impl Definition { .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { ::arcana::es::adapter::codegen::futures::StreamExt::map( - >::transform(self, __event, __context), + >:: + transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { ::std::result::Result::map_err( @@ -569,6 +568,26 @@ impl Definition { }) .collect() } + + /// TODO + #[must_use] + pub fn context_bound(&self) -> TokenStream { + self.variants + .iter() + .filter_map(|var| var.fields.iter().next()) + .map(|f| { + quote! { + >:: + Context<__Impl> + } + }) + .fold(None, |acc, var_ty| { + Some(acc.map( + |acc| quote! { ::arcana::es::adapter::And<#var_ty, #acc> }, + ).unwrap_or(var_ty)) + }) + .unwrap_or_default() + } } #[cfg(test)] diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 425817d..6752beb 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -2,12 +2,8 @@ pub mod chat; pub mod email; pub mod message; -use arcana::{ - es, - es::adapter::{self, transformer::Transformer, And}, -}; +use arcana::es; use derive_more::From; -use futures::stream::{LocalBoxStream, StreamExt as _}; use crate::event; @@ -18,72 +14,6 @@ pub enum Event { Email(EmailEvent), } -impl Transformer for adapter::Wrapper - where - A: adapter::WithError, - Self: Transformer - + Transformer - + Transformer, - A::Error: From<>::Error> - + From<>::Error> - + From<>::Error> - + 'static, - A::Transformed: From<>::Transformed> - + From<>::Transformed> - + From<>::Transformed> - + 'static, -{ - type Context = And< - >::Context, - And< - >::Context, - >::Context< - Impl, - >, - >, - >; - - type Error = A::Error; - - type Transformed = A::Transformed; - - type TransformedStream<'out, Ctx: 'static> = - LocalBoxStream<'out, Result>; - - fn transform<'me, 'ctx, 'out, Ctx>( - &'me self, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out, Ctx> - where - 'me: 'out, - 'ctx: 'out, - Ctx: 'static, - { - match event { - Event::Chat(ev) => { - Transformer::::transform(self, ev, context) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - Event::Message(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - Event::Email(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - } - } -} - #[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), @@ -91,185 +21,14 @@ pub enum ChatEvent { PrivateCreated(event::chat::private::Created), } -impl Transformer for adapter::Wrapper - where - A: adapter::WithError, - Self: Transformer - + Transformer - + Transformer, - A::Error: From<>::Error> - + From<>::Error> - + From<>::Error> - + 'static, - A::Transformed: From<>::Transformed> - + From<>::Transformed> - + From<>::Transformed> - + 'static, -{ - type Context = And< - >::Context, - And< - >::Context, - >::Context< - Impl, - >, - >, - >; - - type Error = A::Error; - - type Transformed = A::Transformed; - - type TransformedStream<'out, Ctx: 'static> = - LocalBoxStream<'out, Result>; - - fn transform<'me, 'ctx, 'out, Ctx>( - &'me self, - event: ChatEvent, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out, Ctx> - where - 'me: 'out, - 'ctx: 'out, - Ctx: 'static, - { - match event { - ChatEvent::Created(ev) => { - Transformer::::transform(self, ev, context) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - ChatEvent::PublicCreated(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - ChatEvent::PrivateCreated(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - } - } -} - #[derive(Debug, es::Event, From)] pub enum MessageEvent { Posted(event::message::Posted), } -impl Transformer for adapter::Wrapper - where - A: adapter::WithError, - Self: Transformer, - A::Error: From<>::Error> - + 'static, - A::Transformed: From<>::Transformed> - + 'static, -{ - type Context = - >::Context; - - type Error = A::Error; - - type Transformed = A::Transformed; - - type TransformedStream<'out, Ctx: 'static> = - LocalBoxStream<'out, Result>; - - fn transform<'me, 'ctx, 'out, Ctx>( - &'me self, - event: MessageEvent, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out, Ctx> - where - 'me: 'out, - 'ctx: 'out, - Ctx: 'static, - { - match event { - MessageEvent::Posted(ev) => { - Transformer::::transform(self, ev, context) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - } - } -} - #[derive(Debug, es::Event, From)] pub enum EmailEvent { Added(event::email::Added), Confirmed(event::email::Confirmed), AddedAndConfirmed(event::email::v1::AddedAndConfirmed), } - -impl Transformer for adapter::Wrapper -where - A: adapter::WithError, - Self: Transformer - + Transformer - + Transformer, - A::Error: From<>::Error> - + From<>::Error> - + From<>::Error> - + 'static, - A::Transformed: From<>::Transformed> - + From<>::Transformed> - + From<>::Transformed> - + 'static, -{ - type Context = And< - >::Context, - And< - >::Context, - >::Context< - Impl, - >, - >, - >; - - type Error = A::Error; - - type Transformed = A::Transformed; - - type TransformedStream<'out, Ctx: 'static> = - LocalBoxStream<'out, Result>; - - fn transform<'me, 'ctx, 'out, Ctx>( - &'me self, - event: EmailEvent, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out, Ctx> - where - 'me: 'out, - 'ctx: 'out, - Ctx: 'static, - { - match event { - EmailEvent::Added(ev) => { - Transformer::::transform(self, ev, context) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - EmailEvent::Confirmed(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - EmailEvent::AddedAndConfirmed(ev) => { - Transformer::::transform( - self, ev, context, - ) - .map(|res| res.map(Into::into).map_err(Into::into)) - .boxed_local() - } - } - } -} From 08acc4935ac7e085b86e231aef681261b980346c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 11:14:28 +0300 Subject: [PATCH 066/104] Bootstrap working version [skip ci] --- codegen/impl/src/es/event/transformer.rs | 14 ++--- examples/chat/src/storage/chat.rs | 73 +++++++----------------- examples/chat/src/storage/email.rs | 68 ++++++---------------- examples/chat/src/storage/message.rs | 68 ++++++---------------- 4 files changed, 63 insertions(+), 160 deletions(-) diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index cc4df77..d4530ad 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -173,17 +173,17 @@ impl Definition { #[must_use] pub fn impl_strategies(&self) -> TokenStream { let mut generics = self.generics.clone(); - generics.params.push(parse_quote! { __Ctx }); - generics.make_where_clause().predicates.push( - parse_quote! { Self: ::arcana::es::adapter::WithError<__Ctx> }, - ); + generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: ::arcana::es::adapter::WithError }); generics.make_where_clause().predicates.push(parse_quote! { >::Transformed: 'static + WithError>::Transformed: 'static }); generics.make_where_clause().predicates.push(parse_quote! { >::Error: 'static + WithError>::Error: 'static }); let (impl_gen, _, where_cl) = generics.split_for_impl(); @@ -198,7 +198,7 @@ impl Definition { .map(|(ev, strategy)| { quote! { impl#impl_gen ::arcana::es::adapter::transformer:: - WithStrategy<#ev, __Ctx> for #adapter#type_gen #where_cl + WithStrategy<#ev> for #adapter#type_gen #where_cl { type Strategy = #strategy; } diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 6a44db7..fd931eb 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -2,68 +2,35 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::{ - self, - strategy::{AsIs, Initialized, Into, Skip}, - }, + transformer::strategy::{AsIs, Initialized, Into, Skip}, + Transformer, }; use crate::event; +#[derive(Debug, Transformer)] +#[transformer( + Initialized => ( + event::chat::public::Created, + event::chat::private::Created, + ), + AsIs => event::message::Posted, + Skip => ( + event::email::v1::AddedAndConfirmed, + event::email::Confirmed, + event::email::Added, + ), + Initialized> => ( + event::chat::v1::Created, + ), +)] +pub struct Adapter; + impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Chat; } -// #[derive(Debug, Transformer)] -// #[transformer( -// Initialized => ( -// event::chat::public::Created, -// event::chat::private::Created, -// ), -// AsIs => event::message::Posted, -// Skip => ( -// event::email::v1::AddedAndConfirmed, -// event::email::Confirmed, -// event::email::Added, -// ), -// Initialized> => ( -// event::chat::v1::Created, -// ), -// )] -#[derive(Debug)] -pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = AsIs; -} - -impl transformer::WithStrategy - for Adapter -{ - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Initialized>; -} - // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index b37794a..a736bfe 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -3,10 +3,8 @@ use std::{array, convert::Infallible}; use arcana::es::{ adapter::{ self, - transformer::{ - self, - strategy::{AsIs, Initialized, Skip, Split, Splitter}, - }, + transformer::strategy::{AsIs, Initialized, Skip, Split, Splitter}, + Transformer, }, event::Initial, }; @@ -14,57 +12,27 @@ use either::Either; use crate::event; +#[derive(Debug, Transformer)] +#[transformer( + Initialized => event::email::Added, + AsIs => event::email::Confirmed, + Skip => ( + event::chat::public::Created, + event::chat::private::Created, + event::chat::v1::Created, + event::message::Posted, + ), + Split> => ( + event::email::v1::AddedAndConfirmed, + ), +)] +pub struct Adapter; + impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Email; } -// #[derive(Debug, Transformer)] -// #[transformer( -// Initialized => event::email::Added, -// AsIs => event::email::Confirmed, -// Skip => ( -// event::chat::public::Created, -// event::chat::private::Created, -// event::chat::v1::Created, -// event::message::Posted, -// ), -// Split> => ( -// event::email::v1::AddedAndConfirmed, -// ), -// )] -pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = AsIs; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy - for Adapter -{ - type Strategy = Split>; -} - impl Splitter< event::email::v1::AddedAndConfirmed, diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 973b6c6..4fa13ad 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -2,61 +2,29 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::{ - self, - strategy::{Initialized, Skip}, - }, + transformer::strategy::{Initialized, Skip}, + Transformer, }; use crate::event; +#[derive(Debug, Transformer)] +#[transformer( + Initialized => ( + event::message::Posted, + ), + Skip => ( + event::chat::public::Created, + event::chat::private::Created, + event::chat::v1::Created, + event::email::Added, + event::email::Confirmed, + event::email::v1::AddedAndConfirmed, + ), +)] +pub struct Adapter; + impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Message; } - -// #[derive(Debug, Transformer)] -// #[transformer( -// Initialized => ( -// event::message::Posted, -// ), -// Skip => ( -// event::chat::public::Created, -// event::chat::private::Created, -// event::chat::v1::Created, -// event::email::Added, -// event::email::Confirmed, -// event::email::v1::AddedAndConfirmed, -// ), -// )] -pub struct Adapter; - -impl transformer::WithStrategy for Adapter { - type Strategy = Initialized; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy for Adapter { - type Strategy = Skip; -} - -impl transformer::WithStrategy - for Adapter -{ - type Strategy = Skip; -} From fe266f2b8be10f3f33d431292333ea71c9e0327c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 12:14:20 +0300 Subject: [PATCH 067/104] Relax trait bounds [skip ci] --- codegen/impl/src/es/event/mod.rs | 4 +- core/src/es/adapter/mod.rs | 19 +++++---- core/src/es/adapter/transformer/mod.rs | 4 +- core/src/es/adapter/transformer/strategy.rs | 47 ++++++++------------- src/es/adapter/mod.rs | 9 ++-- 5 files changed, 34 insertions(+), 49 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index f2b2c2f..9622131 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -418,7 +418,7 @@ impl Definition { type Error = <__A as ::arcana::es::adapter::WithError>::Error; type Transformed = <__A as ::arcana::es::adapter::WithError>::Transformed; - type TransformedStream<'out, __Ctx: 'static> = #transformed; + type TransformedStream<'out, __Ctx: 'out> = #transformed; fn transform<'me, 'ctx, 'out, __Ctx>( &'me self, @@ -429,7 +429,7 @@ impl Definition { where 'me: 'out, 'ctx: 'out, - __Ctx: 'static, + __Ctx: 'out, { match __event { #inner_match diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 2633845..1863e70 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -67,7 +67,7 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'out, Ctx: 'static>: Stream< + type TransformedStream<'out, Ctx: 'out>: Stream< Item = Result< >::Transformed, >::Error, @@ -86,7 +86,7 @@ pub trait Adapter { where 'me: 'out, 'ctx: 'out, - Context: 'static; + Context: 'out; } impl Adapter for A @@ -101,7 +101,7 @@ where { type Error = ::Error; type Transformed = ::Transformed; - type TransformedStream<'out, Ctx: 'static> = + type TransformedStream<'out, Ctx: 'out> = TransformedStream<'out, Wrapper, Events, Ctx>; fn transform_all<'me, 'ctx, 'out, Ctx>( @@ -112,17 +112,18 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { TransformedStream::new(RefCast::ref_cast(self), events, context) } } -#[pin_project] /// [`Stream`] for [`Adapter`] blanket impl. +#[allow(explicit_outlives_requirements)] +#[pin_project] pub struct TransformedStream<'out, Adapter, Events, Ctx> where - Ctx: 'static, + Ctx: 'out, Events: Stream, Adapter: Transformer, >::Context: Correct, @@ -139,7 +140,7 @@ where impl<'out, Adapter, Events, Ctx> fmt::Debug for TransformedStream<'out, Adapter, Events, Ctx> where - Ctx: fmt::Debug + 'static, + Ctx: fmt::Debug + 'out, Events: fmt::Debug + Stream, Adapter: fmt::Debug + Transformer, >::Context: Correct, @@ -165,7 +166,7 @@ type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> where - Ctx: 'static, + Ctx: 'out, Events: Stream, Adapter: Transformer, >::Context: Correct, @@ -183,7 +184,7 @@ where impl<'out, Adapter, Events, Ctx> Stream for TransformedStream<'out, Adapter, Events, Ctx> where - Ctx: 'static, + Ctx: 'out, Events: Stream, Adapter: Transformer + WithError, >::Context: Correct, diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index ca38a01..1588470 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -39,7 +39,7 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'out, Ctx: 'static>: Stream< + type TransformedStream<'out, Ctx: 'out>: Stream< Item = Result< >::Transformed, >::Error, @@ -58,7 +58,7 @@ pub trait Transformer { where 'me: 'out, 'ctx: 'out, - Ctx: 'static; + Ctx: 'out; } /// Instead of implementing [`Transformer`] manually, you can use this trait diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index cc04701..ffecd85 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -35,7 +35,7 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - type TransformedStream<'out, Ctx: 'static>: Stream< + type TransformedStream<'out, Ctx: 'out>: Stream< Item = Result< >::Transformed, >::Error, @@ -54,7 +54,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static; + Ctx: 'out; } impl Transformer for adapter::Wrapper @@ -71,11 +71,10 @@ where type Transformed = >::Transformed; - type TransformedStream<'out, Ctx: 'static> = - >::TransformedStream< - 'out, - Ctx, - >; + type TransformedStream<'out, Ctx: 'out> = >::TransformedStream<'out, Ctx>; fn transform<'me, 'ctx, 'out, Ctx>( &'me self, @@ -85,7 +84,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { >::transform( &self.0, event, context, @@ -114,7 +113,7 @@ where type Transformed = event::Initial; - type TransformedStream<'out, Ctx: 'static> = stream::MapOk< + type TransformedStream<'out, Ctx: 'out> = stream::MapOk< InnerStrategy::TransformedStream<'out, Ctx>, WrapInitial, >; @@ -127,7 +126,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) } @@ -156,7 +155,7 @@ where type Context = Any; type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out, Ctx: 'static> = + type TransformedStream<'out, Ctx: 'out> = stream::Empty>; fn transform<'me, 'ctx, 'out, Ctx>( @@ -167,7 +166,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { stream::empty() } @@ -186,7 +185,7 @@ where type Context = Any; type Error = Infallible; type Transformed = Event; - type TransformedStream<'out, Ctx: 'static> = + type TransformedStream<'out, Ctx: 'out> = stream::Once>>; fn transform<'me, 'ctx, 'out, Ctx>( @@ -197,7 +196,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { stream::once(future::ready(Ok(event))) } @@ -221,7 +220,7 @@ where type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'out, Ctx: 'static> = stream::MapOk< + type TransformedStream<'out, Ctx: 'out> = stream::MapOk< InnerStrategy::TransformedStream<'out, Ctx>, IntoFn, >; @@ -234,7 +233,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } @@ -274,7 +273,7 @@ where type Context = Any; type Error = Infallible; type Transformed = ::Item; - type TransformedStream<'out, Ctx: 'static> = + type TransformedStream<'out, Ctx: 'out> = SplitStream; fn transform<'me, 'ctx, 'out, Ctx>( @@ -285,7 +284,7 @@ where where 'me: 'out, 'ctx: 'out, - Ctx: 'static, + Ctx: 'out, { stream::iter(adapter.split(event)).map(Ok) } @@ -317,15 +316,3 @@ where R: Correct, { } - -/// TODO -#[macro_export] -macro_rules! and { - ($head: ty $(,)?) => { $head }; - ($head: ty, $($tail: ty),* $(,)?) => { - $crate::es::adapter::transformer::strategy::And< - $head, - $crate::and!($($tail),*) - > - }; -} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 105270a..3e8ecd1 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -6,12 +6,9 @@ pub mod transformer; pub use self::transformer::Transformer; #[doc(inline)] -pub use arcana_core::{ - and, - es::adapter::{ - transformer::strategy::{And, Any}, - Adapter, Correct, WithError, Wrapper, - }, +pub use arcana_core::es::adapter::{ + transformer::strategy::{And, Any}, + Adapter, Correct, WithError, Wrapper, }; #[cfg(feature = "derive")] From 0dba918b72d9105cc9ea4c724a985d59309bf3da Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 12:57:01 +0300 Subject: [PATCH 068/104] Relax trait bounds [skip ci] --- core/src/es/adapter/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 1863e70..9fe0266 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -72,7 +72,9 @@ pub trait Adapter { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + Events: 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// @@ -91,7 +93,7 @@ pub trait Adapter { impl Adapter for A where - Events: Stream + 'static, + Events: Stream, A: WithError, Wrapper: Transformer + 'static, ::Transformed: @@ -101,8 +103,10 @@ where { type Error = ::Error; type Transformed = ::Transformed; - type TransformedStream<'out, Ctx: 'out> = - TransformedStream<'out, Wrapper, Events, Ctx>; + type TransformedStream<'out, Ctx: 'out> + where + Events: 'out, + = TransformedStream<'out, Wrapper, Events, Ctx>; fn transform_all<'me, 'ctx, 'out, Ctx>( &'me self, From 328cf1f922820bf3d2fb6875679c934c55e30956 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 15:50:50 +0300 Subject: [PATCH 069/104] Clean up Event derive macro Transformer portion [skip ci] --- codegen/impl/src/es/event/mod.rs | 248 +++++++++----------- codegen/impl/src/es/event/transformer.rs | 6 +- core/src/es/adapter/mod.rs | 42 ++-- core/src/es/adapter/transformer/strategy.rs | 7 +- examples/chat/src/main.rs | 12 +- examples/chat/src/storage/chat.rs | 2 +- examples/chat/src/storage/email.rs | 2 +- examples/chat/src/storage/message.rs | 2 +- src/es/adapter/mod.rs | 2 +- 9 files changed, 147 insertions(+), 176 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 9622131..76d0f9b 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -7,7 +7,7 @@ use std::{convert::TryFrom, iter}; use proc_macro2::TokenStream; use quote::quote; -use syn::{parse_quote, spanned::Spanned as _}; +use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned as _, Token}; use synthez::{ParseAttrs, ToTokens}; /// Expands `#[derive(Event)]` macro. @@ -146,6 +146,11 @@ impl Definition { quote! { < #( #generics ),* > } } + /// TODO + pub fn variants_types(&self) -> impl Iterator { + self.variants.iter().flat_map(|v| &v.fields) + } + /// Generates code to derive [`Event`][0] trait, by simply matching over /// each enum variant, which is expected to be itself an [`Event`][0] /// implementer. @@ -201,8 +206,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); let turbofish_gens = ty_gens.as_turbofish(); - let var_ty = - self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); + let var_ty = self.variants_types(); let mut ext_gens = self.generics.clone(); ext_gens.params.push(parse_quote! { __S }); @@ -252,12 +256,7 @@ impl Definition { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let var_ty = self - .variants - .iter() - .flat_map(|v| &v.fields) - .map(|f| &f.ty) - .collect::>(); + let var_ty = self.variants_types().collect::>(); // TODO: Use `Self::__arcana_events()` inside impl instead of type // params substitution, once rust-lang/rust#57775 is resolved: @@ -314,118 +313,41 @@ impl Definition { } } - /// TODO - #[allow(clippy::too_many_lines)] + /// Generates code to derive [`Transformer`][0] trait, by matching over each + /// enum variant, which is expected to have itself [`Transformer`][0] + /// implementation. + /// + /// [0]: arcana_core::es::adapter::Transformer #[must_use] pub fn impl_transformer(&self) -> TokenStream { + let adapter_path = quote! { ::arcana::es::adapter }; let event = &self.ident; - let inner_match = self.inner_match(); - let transformed = self.transformed_stream(); - let context_bound = self.context_bound(); - - let mut generics = self.generics.clone(); - generics.params.push(parse_quote! { __A }); - generics.make_where_clause().predicates.push(parse_quote! { - __A: ::arcana::es::adapter::WithError - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { ::arcana::es::adapter::Transformer<#ty> } - }); - let self_bound = quote! { Self: #( #adapter_constrains )+* }; - parse_quote! { #self_bound } - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { - ::std::convert::From< - >:: - Transformed - > - } - }); - let transformed_bound = quote! { - <__A as ::arcana::es::adapter::WithError>::Transformed: - #( #adapter_constrains )+* - }; - parse_quote! { #transformed_bound + 'static } - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { - ::std::convert::From< - >:: - Error - > - } - }); - let err_bound = quote! { - <__A as ::arcana::es::adapter::WithError>::Error: - #( #adapter_constrains )+* - }; - parse_quote! { #err_bound + 'static } - }); - - for bound in self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - parse_quote! { - >:: - Error: 'static - } - }) - { - generics.make_where_clause().predicates.push(bound); - } - for bound in self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - parse_quote! { - >:: - Transformed: 'static - } - }) - { - generics.make_where_clause().predicates.push(bound); - } - - let (_, type_gen, _) = self.generics.split_for_impl(); + let generics = self.transformer_generics(); let (impl_gen, _, where_clause) = generics.split_for_impl(); + let (_, type_gen, _) = self.generics.split_for_impl(); + + let context_bound = self.transformer_context_bound(); + let transformed = self.transformed_stream(); + let inner_match = self.transformer_inner_match(); quote! { #[automatically_derived] impl#impl_gen ::arcana::es::adapter::Transformer<#event#type_gen> - for ::arcana::es::adapter::Wrapper<__A> #where_clause + for #adapter_path::Wrapper<__A> #where_clause { type Context<__Impl> = #context_bound; - type Error = <__A as ::arcana::es::adapter::WithError>::Error; + type Error = <__A as #adapter_path::Returning>::Error; type Transformed = - <__A as ::arcana::es::adapter::WithError>::Transformed; + <__A as #adapter_path::Returning>::Transformed; type TransformedStream<'out, __Ctx: 'out> = #transformed; fn transform<'me, 'ctx, 'out, __Ctx>( &'me self, __event: #event#type_gen, __context: &'ctx __Ctx, - ) -> >::TransformedStream<'out, __Ctx> + ) -> >:: + TransformedStream<'out, __Ctx> where 'me: 'out, 'ctx: 'out, @@ -454,34 +376,32 @@ impl Definition { /// [`stream::Map`]: futures::stream::Map #[must_use] pub fn transformed_stream(&self) -> TokenStream { + let adapter_path = quote! { ::arcana::es::adapter }; let event = &self.ident; let transformed_stream = |from: &syn::Type| { quote! { - ::arcana::es::adapter::codegen::futures::stream::Map< - >:: + #adapter_path::codegen::futures::stream::Map< + >:: TransformedStream<'out, __Ctx>, fn( ::std::result::Result< - >::Transformed, - >::Error, + >:: + Transformed, + >:: + Error, >, ) -> ::std::result::Result< - >::Transformed, - >::Error, + >:: + Transformed, + >:: + Error, > > } }; - self.variants - .iter() - .rev() - .filter_map(|var| var.fields.iter().next()) + self.variants_types() .fold(None, |acc, field| { let variant_stream = transformed_stream(&field.ty); Some( @@ -503,17 +423,17 @@ impl Definition { /// Generates code for implementation of a [`Transformer::transform()`][0] /// fn. /// - /// Generated code matches over every [`Event`]'s variant and makes it - /// compatible with [`Self::transformed_stream()`] type with - /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] - /// combinators. + /// Matches over every [`Event`]'s variant and makes it compatible with + /// [`Self::transformed_stream()`] type with [`StreamExt::left_stream()`] + /// and [`StreamExt::right_stream()`] combinators. /// /// [0]: arcana_core::es::adapter::Transformer::transform - /// [`Event`]: trait@arcana_core::es::Event + /// [`Event`]: arcana_core::es::Event /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() #[must_use] - pub fn inner_match(&self) -> TokenStream { + pub fn transformer_inner_match(&self) -> TokenStream { + let adapter_path = quote! { ::arcana::es::adapter }; let event = &self.ident; self.variants @@ -524,8 +444,8 @@ impl Definition { .enumerate() .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt::map( - >:: + #adapter_path::codegen::futures::StreamExt::map( + >:: transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { @@ -543,12 +463,10 @@ impl Definition { }; let right_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - right_stream + #adapter_path::futures::StreamExt::right_stream }; let left_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: - left_stream + #adapter_path::codegen::futures::StreamExt::left_stream }; let left_stream_count = (i == self.variants.len() - 1).then(|| 0).unwrap_or(1); @@ -569,25 +487,77 @@ impl Definition { .collect() } - /// TODO + /// Generates [`Transformer::Context`][1] type to constrain `Ctx` in + /// [`transform()`][2] method. + /// + /// [1]: arcana_core::es::adapter::Transformer::Context + /// [2]: arcana_core::es::adapter::Transformer::transform #[must_use] - pub fn context_bound(&self) -> TokenStream { - self.variants - .iter() - .filter_map(|var| var.fields.iter().next()) + pub fn transformer_context_bound(&self) -> TokenStream { + let adapter_path = quote! { ::arcana::es::adapter }; + self.variants_types() .map(|f| { quote! { - >:: - Context<__Impl> + >::Context<__Impl> } }) .fold(None, |acc, var_ty| { - Some(acc.map( - |acc| quote! { ::arcana::es::adapter::And<#var_ty, #acc> }, - ).unwrap_or(var_ty)) + Some( + acc.map(|acc| quote! { #adapter_path::And<#var_ty, #acc> }) + .unwrap_or(var_ty), + ) }) .unwrap_or_default() } + + /// Generates [`syn::Generics`] for [`Transformer`] impl. + /// + /// 1. Adds `__A` generic parameter for [`Adapter`]; + /// 2. Ensures `__A` implements [`Returning`]; + /// 3. Ensures [`Wrapper`]`<__A>` implements [`Transformer`] for every enum + /// variant; + /// 4. Ensures [`Transformed`] and [`Error`] are implementing [`From`] for + /// every enum variant's [`Transformed`] and [`Error`] and they are all + /// `'static`. + /// + /// [`Adapter`]: arcana_core::es::Adapter + /// [`Error`]: arcana_core::es::adapter::Transformer::Error + /// [`Returning`]: arcana_core::es::adapter::Returning + /// [`Transformed`]: arcana_core::es::adapter::Transformer::Transformed + /// [`Transformer`]: arcana_core::es::adapter::Transformer + /// [`Wrapper`]: arcana_core::es::adapter::Wrapper + #[must_use] + pub fn transformer_generics(&self) -> syn::Generics { + let adapter_path = quote! { ::arcana::es::adapter }; + let var_ty = self.variants_types().collect::>(); + + let bounds: Punctuated = parse_quote! { + __A: #adapter_path::Returning, + Self: #( #adapter_path::Transformer<#var_ty> )+*, + <__A as #adapter_path::Returning>::Transformed: + #( ::std::convert::From< + >::Transformed + > )+* + + 'static, + <__A as #adapter_path::Returning>::Error: + #( ::std::convert::From< + >::Error + > )+* + + 'static, + #( + >::Transformed: + 'static, + >::Error: + 'static, + )* + }; + + let mut generics = self.generics.clone(); + generics.params.push(parse_quote! { __A }); + generics.make_where_clause().predicates.extend(bounds); + + generics + } } #[cfg(test)] diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/transformer.rs index d4530ad..2ac60f7 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/transformer.rs @@ -176,14 +176,14 @@ impl Definition { generics .make_where_clause() .predicates - .push(parse_quote! { Self: ::arcana::es::adapter::WithError }); + .push(parse_quote! { Self: ::arcana::es::adapter::Returning }); generics.make_where_clause().predicates.push(parse_quote! { ::Transformed: 'static + Returning>::Transformed: 'static }); generics.make_where_clause().predicates.push(parse_quote! { ::Error: 'static + Returning>::Error: 'static }); let (impl_gen, _, where_cl) = generics.split_for_impl(); diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 9fe0266..6bdad73 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -15,23 +15,14 @@ use ref_cast::RefCast; #[doc(inline)] pub use self::transformer::Transformer; -/// TODO -pub trait WithError { - /// TODO - type Error; - - /// TODO - type Transformed; -} - /// TODO #[derive(Debug, RefCast)] #[repr(transparent)] pub struct Wrapper(pub A); -impl WithError for Wrapper +impl Returning for Wrapper where - A: WithError, + A: Returning, { type Error = A::Error; type Transformed = A::Transformed; @@ -91,18 +82,27 @@ pub trait Adapter { Context: 'out; } +/// TODO +pub trait Returning { + /// TODO + type Error; + + /// TODO + type Transformed; +} + impl Adapter for A where + A: Returning, Events: Stream, - A: WithError, Wrapper: Transformer + 'static, - ::Transformed: + ::Transformed: From< as Transformer>::Transformed>, - ::Error: + ::Error: From< as Transformer>::Error>, { - type Error = ::Error; - type Transformed = ::Transformed; + type Error = ::Error; + type Transformed = ::Transformed; type TransformedStream<'out, Ctx: 'out> where Events: 'out, @@ -190,16 +190,16 @@ impl<'out, Adapter, Events, Ctx> Stream where Ctx: 'out, Events: Stream, - Adapter: Transformer + WithError, + Adapter: Transformer + Returning, >::Context: Correct, - ::Transformed: + ::Transformed: From<>::Transformed>, - ::Error: + ::Error: From<>::Error>, { type Item = Result< - ::Transformed, - ::Error, + ::Transformed, + ::Error, >; fn poll_next( diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index ffecd85..4ce037d 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -16,10 +16,7 @@ use super::{Transformer, WithStrategy}; /// Generalized [`Transformer`] for [`Versioned`] events. /// /// [`Versioned`]: event::Versioned -pub trait Strategy -where - Event: event::Versioned, -{ +pub trait Strategy { /// TODO type Context: Correct; @@ -148,7 +145,7 @@ pub struct Skip; impl Strategy for Skip where Event: event::Versioned, - Adapter: adapter::WithError, + Adapter: adapter::Returning, Adapter::Transformed: 'static, Adapter::Error: 'static, { diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 0e61fa3..29285cb 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -7,17 +7,21 @@ mod storage; use std::array; use arcana::es::{event::Sourced, EventAdapter as _}; -use futures::{stream, Stream, TryStreamExt as _}; +use futures::{stream, Stream, StreamExt as _, TryStreamExt as _}; #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::main] async fn main() { + let ctx = (); // can be any type + let mut events = stream::repeat_with(incoming_events).flatten(); + let events = &mut events; + let mut chat = Option::::None; let mut message = Option::::None; let mut email = Option::::None; let chat_events = storage::chat::Adapter - .transform_all(incoming_events(), &()) + .transform_all(events.take(5), &ctx) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -34,7 +38,7 @@ async fn main() { ); let email_events = storage::email::Adapter - .transform_all(incoming_events(), &()) + .transform_all(events.take(5), &ctx) .inspect_ok(|ev| email.apply(ev)) .try_collect::>() .await @@ -51,7 +55,7 @@ async fn main() { ); let message_events = storage::message::Adapter - .transform_all(incoming_events(), &()) + .transform_all(events.take(5), &ctx) .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index fd931eb..62bfc24 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -26,7 +26,7 @@ use crate::event; )] pub struct Adapter; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Chat; } diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index a736bfe..4611f3d 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -28,7 +28,7 @@ use crate::event; )] pub struct Adapter; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Email; } diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 4fa13ad..811b538 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -24,7 +24,7 @@ use crate::event; )] pub struct Adapter; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Message; } diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 3e8ecd1..97fce82 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -8,7 +8,7 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{ transformer::strategy::{And, Any}, - Adapter, Correct, WithError, Wrapper, + Adapter, Correct, Returning, Wrapper, }; #[cfg(feature = "derive")] From 7057ddd8436104702e293f84c85d4b56e8173907 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Sep 2021 15:53:39 +0300 Subject: [PATCH 070/104] Clean up Event derive macro Transformer portion [skip ci] --- codegen/impl/src/es/event/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 76d0f9b..f56eba8 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -146,7 +146,9 @@ impl Definition { quote! { < #( #generics ),* > } } - /// TODO + /// Returns [`Iterator`] over each enum variant [`Field`]. + /// + /// [`Field`]: syn::Field pub fn variants_types(&self) -> impl Iterator { self.variants.iter().flat_map(|v| &v.fields) } From 5db7732d72f52bb5fd534a9cd8c5fafcbd1390db Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Sep 2021 11:35:55 +0300 Subject: [PATCH 071/104] WIP [skip ci] --- codegen/impl/Cargo.toml | 1 + codegen/impl/src/es/event/mod.rs | 355 ++++++++++++------ .../es/event/{transformer.rs => strategy.rs} | 158 +++----- codegen/shim/src/lib.rs | 6 +- codegen/src/es/mod.rs | 2 +- .../src/es/{transformer.rs => strategy.rs} | 2 +- core/src/es/adapter/mod.rs | 46 ++- core/src/es/adapter/transformer/mod.rs | 11 +- core/src/es/adapter/transformer/strategy.rs | 97 ++++- examples/chat/src/main.rs | 4 +- examples/chat/src/storage/chat.rs | 17 +- examples/chat/src/storage/email.rs | 19 +- examples/chat/src/storage/message.rs | 40 +- src/es/adapter/transformer/mod.rs | 4 - src/es/adapter/transformer/strategy.rs | 7 +- 15 files changed, 467 insertions(+), 302 deletions(-) rename codegen/impl/src/es/event/{transformer.rs => strategy.rs} (81%) rename codegen/src/es/{transformer.rs => strategy.rs} (61%) diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index 574fcc0..de5a3e5 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -17,6 +17,7 @@ readme = "README.md" doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] +itertools = "0.10" proc-macro2 = { version = "1.0.4", default-features = false } quote = { version = "1.0.9", default-features = false } syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index c69b4a7..dbf5a1e 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -1,13 +1,13 @@ //! `#[derive(Event)]` macro implementation. -pub mod transformer; +pub mod strategy; pub mod versioned; use std::{convert::TryFrom, iter}; use proc_macro2::TokenStream; use quote::quote; -use syn::{parse_quote, spanned::Spanned as _}; +use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned as _}; use synthez::{ParseAttrs, ToTokens}; /// Expands `#[derive(Event)]` macro. @@ -39,8 +39,8 @@ pub struct VariantAttrs { #[to_tokens(append( impl_event, impl_event_sourced, + impl_transformer, gen_uniqueness_glue_code, - impl_transformer ))] pub struct Definition { /// [`syn::Ident`](struct@syn::Ident) of this enum's type. @@ -146,6 +146,14 @@ impl Definition { quote! { < #( #generics ),* > } } + /// Returns [`Iterator`] of enum variant [`syn::Type`]s. + #[must_use] + pub fn variant_types(&self) -> impl DoubleEndedIterator { + self.variants + .iter() + .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) + } + /// Generates code to derive [`Event`][0] trait, by simply matching over /// each enum variant, which is expected to be itself an [`Event`][0] /// implementer. @@ -201,8 +209,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); let turbofish_gens = ty_gens.as_turbofish(); - let var_ty = - self.variants.iter().flat_map(|v| &v.fields).map(|f| &f.ty); + let var_ty = self.variant_types(); let mut ext_gens = self.generics.clone(); ext_gens.params.push(parse_quote! { __S }); @@ -252,12 +259,7 @@ impl Definition { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let var_ty = self - .variants - .iter() - .flat_map(|v| &v.fields) - .map(|f| &f.ty) - .collect::>(); + let var_ty = self.variant_types().collect::>(); // TODO: Use `Self::__arcana_events()` inside impl instead of type // params substitution, once rust-lang/rust#57775 is resolved: @@ -314,8 +316,12 @@ impl Definition { } } - /// TODO - #[allow(clippy::too_many_lines)] + /// Generates code to derive [`Transformer`][0] trait for any [wrapped][1] + /// [`Adapter`][2], which can transform every enum variant. + /// + /// [0]: arcana_core::es::adapter::Transformer + /// [1]: arcana_core::es::adapter::Wrapper + /// [2]: arcana_core::es::Adapter #[must_use] pub fn impl_transformer(&self) -> TokenStream { let event = &self.ident; @@ -323,95 +329,9 @@ impl Definition { let inner_match = self.inner_match(); let transformed = self.transformed_stream(); - let mut generics = self.generics.clone(); - generics.params.push(parse_quote! { __A }); - generics.params.push(parse_quote! { __Ctx }); - generics.make_where_clause().predicates.push(parse_quote! { - __A: ::arcana::es::adapter::WithError<__Ctx> - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { ::arcana::es::adapter::Transformer<#ty, __Ctx> } - }); - let self_bound = quote! { Self: #( #adapter_constrains )+* }; - parse_quote! { #self_bound } - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { - ::std::convert::From< - >::Transformed - > - } - }); - let transformed_bound = quote! { - <__A as ::arcana::es::adapter::WithError<__Ctx>>::Transformed: - #( #adapter_constrains )+* - }; - parse_quote! { #transformed_bound + 'static } - }); - generics.make_where_clause().predicates.push({ - let adapter_constrains = self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - quote! { - ::std::convert::From< - >::Error - > - } - }); - let err_bound = quote! { - <__A as ::arcana::es::adapter::WithError<__Ctx>>::Error: - #( #adapter_constrains )+* - }; - parse_quote! { #err_bound + 'static } - }); - - for bound in self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - parse_quote! { - >::Error: 'static - } - }) - { - generics.make_where_clause().predicates.push(bound); - } - for bound in self - .variants - .iter() - .filter_map(|var| var.fields.iter().next().map(|f| &f.ty)) - .map(|ty| { - parse_quote! { - >::Transformed: 'static - } - }) - { - generics.make_where_clause().predicates.push(bound); - } - - let (_, type_gen, _) = self.generics.split_for_impl(); + let generics = self.transformer_generics(); let (impl_gen, _, where_clause) = generics.split_for_impl(); + let (_, type_gen, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] @@ -419,18 +339,19 @@ impl Definition { #event#type_gen, __Ctx > for ::arcana::es::adapter::Wrapper<__A> #where_clause { - type Error = <__A as ::arcana::es::adapter:: - WithError<__Ctx>>::Error; - type Transformed = <__A as ::arcana::es::adapter:: - WithError<__Ctx>>::Transformed; + type Error = <__A as ::arcana::es::adapter::WithError>:: + Error; + type Transformed = <__A as ::arcana::es::adapter::WithError>:: + Transformed; type TransformedStream<'out> = #transformed; fn transform<'me, 'ctx, 'out>( &'me self, - __event: #event, + __event: #event#type_gen, __context: &'ctx __Ctx, ) -> >::TransformedStream<'out> + Transformer<#event#type_gen, __Ctx>>:: + TransformedStream<'out> where 'me: 'out, 'ctx: 'out, @@ -443,6 +364,52 @@ impl Definition { } } + /// Generates [`syn::Generics`] to for [wrapped][0] [`Adapter`][1], which + /// [`transform`][2]s every enum variant. + /// + /// [0]: arcana_core::es::adapter::Wrapper + /// [1]: arcana_core::es::Adapter + /// [2]: arcana_core::es::adapter::Transformer::transform + #[must_use] + pub fn transformer_generics(&self) -> syn::Generics { + let mut generics = self.generics.clone(); + let var_type = self.variant_types().collect::>(); + + let additional_generic_params: Punctuated< + syn::GenericParam, + syn::Token![,], + > = parse_quote! { + __A, __Ctx + }; + let transformer_bounds: Punctuated< + syn::WherePredicate, + syn::Token![,], + > = parse_quote! { + __A: ::arcana::es::adapter::WithError, + Self: #( ::arcana::es::adapter::Transformer<#var_type, __Ctx> )+*, + <__A as ::arcana::es::adapter::WithError>::Transformed: + #( ::std::convert::From<>::Transformed> +)* + 'static, + <__A as ::arcana::es::adapter::WithError>::Error: + #( ::std::convert::From<>::Error> +)* + 'static, + #( >:: + Transformed: 'static, + >:: + Error: 'static, )* + }; + + generics.params.extend(additional_generic_params); + generics + .make_where_clause() + .predicates + .extend(transformer_bounds); + + generics + } + /// Generates code of [`Transformer::Transformed`][0] associated type. /// /// This is basically a recursive type @@ -458,37 +425,36 @@ impl Definition { /// [`stream::Map`]: futures::stream::Map #[must_use] pub fn transformed_stream(&self) -> TokenStream { - let from = &self.ident; + let event = &self.ident; + let (_, ty_gen, _) = self.generics.split_for_impl(); - let transformed_stream = |event: &syn::Type| { + let transformed_stream = |from: &syn::Type| { quote! { ::arcana::es::adapter::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, + Transformer<#from, __Ctx>>::Transformed, >::Error, + Transformer<#from, __Ctx>>::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<#event#ty_gen, __Ctx>>::Transformed, >::Error, + Transformer<#event#ty_gen, __Ctx>>::Error, > > } }; - self.variants - .iter() + self.variant_types() .rev() - .filter_map(|var| var.fields.iter().next()) - .fold(None, |acc, field| { - let variant_stream = transformed_stream(&field.ty); + .fold(None, |acc, ty| { + let variant_stream = transformed_stream(ty); Some( acc.map(|acc| { quote! { @@ -509,7 +475,7 @@ impl Definition { /// fn. /// /// Generated code matches over every [`Event`]'s variant and makes it - /// compatible with [`Self::transformed_stream()`] type with + /// compatible with [`Definition::transformed_stream()`] type with /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] /// combinators. /// @@ -520,6 +486,8 @@ impl Definition { #[must_use] pub fn inner_match(&self) -> TokenStream { let event = &self.ident; + let (_, ty_gens, _) = self.generics.split_for_impl(); + let turbofish_gens = ty_gens.as_turbofish(); self.variants .iter() @@ -532,7 +500,7 @@ impl Definition { ::arcana::es::adapter::codegen::futures::StreamExt::map( >::transform(self, __event, __context), + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { ::std::result::Result::map_err( @@ -567,7 +535,7 @@ impl Definition { }); quote! { - #event::#variant_ident(__event) => { + #event#turbofish_gens::#variant_ident(__event) => { #transformed_stream }, } @@ -635,6 +603,149 @@ mod spec { } } + #[automatically_derived] + impl<__A, __Ctx> ::arcana::es::adapter::Transformer + for ::arcana::es::adapter::Wrapper<__A> + where + __A: ::arcana::es::adapter::WithError, + Self: + ::arcana::es::adapter::Transformer + + ::arcana::es::adapter::Transformer, + <__A as ::arcana::es::adapter::WithError>::Transformed: + ::std::convert::From< >::Transformed> + + ::std::convert::From< >::Transformed> + + 'static, + <__A as ::arcana::es::adapter::WithError>::Error: + ::std::convert::From< >::Error> + + ::std::convert::From< >::Error> + + 'static, + >::Transformed: 'static, + >::Error: 'static, + >::Transformed: 'static, + >::Error: 'static + { + type Error = <__A as ::arcana::es::adapter::WithError>:: + Error; + type Transformed = <__A as ::arcana::es::adapter::WithError>:: + Transformed; + type TransformedStream<'out> = + ::arcana::es::adapter::codegen::futures::future::Either< + ::arcana::es::adapter::codegen::futures::stream::Map< + >::TransformedStream<'out>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + > + >, + ::arcana::es::adapter::codegen::futures::stream::Map< + >::TransformedStream<'out>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + > + >, + >; + + fn transform<'me, 'ctx, 'out>( + &'me self, + __event: Event, + __context: &'ctx __Ctx, + ) -> >:: + TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + match __event { + Event::File(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + Event::Chat(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { diff --git a/codegen/impl/src/es/event/transformer.rs b/codegen/impl/src/es/event/strategy.rs similarity index 81% rename from codegen/impl/src/es/event/transformer.rs rename to codegen/impl/src/es/event/strategy.rs index cc4df77..8e2fb6b 100644 --- a/codegen/impl/src/es/event/transformer.rs +++ b/codegen/impl/src/es/event/strategy.rs @@ -1,23 +1,21 @@ //! `#[derive(adapter::Transformer)]` macro implementation. -use std::{collections::HashMap, convert::TryFrom, iter}; +use std::{collections::HashMap, convert::TryFrom}; +use itertools::Itertools as _; use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - token, }; -use synthez::{ParseAttrs, Required, Spanning, ToTokens}; +use synthez::{ParseAttrs, ToTokens}; -/// Expands `#[derive(adapter::Transformer)]` macro. +/// Expands `#[derive(Strategy)]` macro. /// /// # Errors /// -/// - If `input` isn't a Rust enum definition; -/// - If some enum variant is not a single-field tuple struct; /// - If failed to parse [`Attrs`]. pub fn derive(input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; @@ -26,103 +24,53 @@ pub fn derive(input: TokenStream) -> syn::Result { Ok(quote! { #definition }) } -/// Helper attributes of `#[derive(adapter::Transformer)]` macro. -#[derive(Debug, Default, ParseAttrs)] -pub struct Attrs { - /// [`Vec`] of [`InnerAttrs`] for generating [`Transformer`][0] trait impls. - /// - /// [0]: arcana_core::es::adapter::Transformer - #[parse(nested)] - pub transformer: Required>, -} - -/// TODO +/// Helper attributes of `#[derive(Strategy)]` macro placed on an enum variant. #[derive(Debug, Default, PartialEq)] -pub struct StrategyAttr { - /// TODO +pub struct Attr { + /// [`Strategies`][0] with corresponding [`VersionedEvent`][1]s. + /// + /// [0]: arcana_core::es::adapter::transformer::Strategy + /// [1]: arcana_core::es::VersionedEvent pub strategies: HashMap>, } -/// TODO -#[derive(Debug)] -pub struct StrategyAttrRepr { - /// TODO - pub strategy: syn::Type, - - /// TODO - pub eq_sign: syn::Token![=], - - /// TODO - pub greater_sign: syn::Token![>], - - /// TODO - pub events: EventsRepr, -} - -/// TODO -#[derive(Debug)] -pub enum EventsRepr { - /// TODO - Many { - /// TODO - paren: token::Paren, - - /// TODO - events: Punctuated, - }, - - /// TODO - Single { - /// TODO - event: syn::Type, - }, -} - -impl Parse for StrategyAttr { +impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let parse_attr = |input: ParseStream<'_>| { - let many = || { + let parenthesized = || { let content; - Ok(EventsRepr::Many { - paren: syn::parenthesized!(content in input), - events: content.parse_terminated(syn::Type::parse)?, - }) + let _ = syn::parenthesized!(content in input); + Ok(content) }; - let single_or_many = || { - many().or_else(|_| -> syn::Result<_> { - Ok(EventsRepr::Single { - event: input.parse()?, - }) - }) + let events = || { + parenthesized().map_or_else( + |_| input.parse().map(|ty| vec![ty]), + |par| { + par.parse_terminated::<_, syn::Token![,]>( + syn::Type::parse, + ) + .map(|ty| ty.into_iter().collect::>()) + }, + ) }; - Ok(StrategyAttrRepr { - strategy: input.parse()?, - eq_sign: input.parse()?, - greater_sign: input.parse()?, - events: single_or_many()?, - }) + let strategy = input.parse()?; + let _ = input.parse::()?; + let _ = input.parse::]>()?; + + Ok((strategy, events()?)) }; let strategies = input .parse_terminated::<_, syn::Token![,]>(parse_attr)? .into_iter() - .map(|repr: StrategyAttrRepr| { - let events = match repr.events { - EventsRepr::Many { events, .. } => { - events.into_iter().collect() - } - EventsRepr::Single { event } => vec![event], - }; - (repr.strategy, events) - }) .collect::>(); Ok(Self { strategies }) } } -impl ParseAttrs for StrategyAttr { +impl ParseAttrs for Attr { fn try_merge(self, another: Self) -> syn::Result { Ok(Self { strategies: self @@ -149,7 +97,10 @@ pub struct Definition { /// [`syn::Generics`] of this enum's type. pub generics: syn::Generics, - /// TODO + /// [`Strategies`][0] with corresponding [`VersionedEvent`][1]s. + /// + /// [0]: arcana_core::es::adapter::transformer::Strategy + /// [1]: arcana_core::es::VersionedEvent pub strategies: HashMap>, } @@ -157,8 +108,7 @@ impl TryFrom for Definition { type Error = syn::Error; fn try_from(input: syn::DeriveInput) -> syn::Result { - let attrs: StrategyAttr = - StrategyAttr::parse_attrs("transformer", &input)?; + let attrs: Attr = Attr::parse_attrs("strategy", &input)?; Ok(Self { adapter: input.ident, @@ -169,22 +119,26 @@ impl TryFrom for Definition { } impl Definition { - /// TODO + /// Generates code to derive [`Strategy`][0] traits. + /// + /// [0]: arcana_core::es::adapter::transformer::Strategy #[must_use] pub fn impl_strategies(&self) -> TokenStream { + let transformed_and_err_bounds: Punctuated< + syn::WherePredicate, + syn::Token![,], + > = parse_quote! { + Self: ::arcana::es::adapter::WithError, + ::Transformed: 'static, + ::Error: 'static, + }; + let mut generics = self.generics.clone(); generics.params.push(parse_quote! { __Ctx }); - generics.make_where_clause().predicates.push( - parse_quote! { Self: ::arcana::es::adapter::WithError<__Ctx> }, - ); - generics.make_where_clause().predicates.push(parse_quote! { - >::Transformed: 'static - }); - generics.make_where_clause().predicates.push(parse_quote! { - >::Error: 'static - }); + generics + .make_where_clause() + .predicates + .extend(transformed_and_err_bounds); let (impl_gen, _, where_cl) = generics.split_for_impl(); let (_, type_gen, _) = self.generics.split_for_impl(); @@ -192,16 +146,14 @@ impl Definition { self.strategies .iter() - .flat_map(|(strategy, events)| { - events.iter().zip(iter::repeat(strategy)) - }) - .map(|(ev, strategy)| { + .sorted_by_key(|(s, _)| s.to_token_stream().to_string()) + .map(|(strategy, ev)| { quote! { - impl#impl_gen ::arcana::es::adapter::transformer:: + #( impl#impl_gen ::arcana::es::adapter::transformer:: WithStrategy<#ev, __Ctx> for #adapter#type_gen #where_cl { type Strategy = #strategy; - } + } )* } }) .collect() diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 3a0cf1f..24abb82 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -160,9 +160,9 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { } /// TODO -#[proc_macro_derive(EventTransformer, attributes(transformer))] -pub fn derive_event_transformer(input: TokenStream) -> TokenStream { - codegen::es::event::transformer::derive(input.into()) +#[proc_macro_derive(Strategy, attributes(strategy))] +pub fn derive_strategy(input: TokenStream) -> TokenStream { + codegen::es::event::strategy::derive(input.into()) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/codegen/src/es/mod.rs b/codegen/src/es/mod.rs index dedc845..71739cb 100644 --- a/codegen/src/es/mod.rs +++ b/codegen/src/es/mod.rs @@ -3,4 +3,4 @@ //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub mod event; -pub mod transformer; +pub mod strategy; diff --git a/codegen/src/es/transformer.rs b/codegen/src/es/strategy.rs similarity index 61% rename from codegen/src/es/transformer.rs rename to codegen/src/es/strategy.rs index d69b6af..7325603 100644 --- a/codegen/src/es/transformer.rs +++ b/codegen/src/es/strategy.rs @@ -2,4 +2,4 @@ //! //! [`Transformer`]: arcana_core::es::adapter::Transformer -pub use arcana_codegen_shim::EventTransformer as Transformer; +pub use arcana_codegen_shim::Strategy; diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 1786bf8..f79f987 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -16,7 +16,7 @@ use ref_cast::RefCast; pub use self::transformer::Transformer; /// TODO -pub trait WithError { +pub trait WithError { /// TODO type Error; @@ -29,10 +29,9 @@ pub trait WithError { #[repr(transparent)] pub struct Wrapper(pub A); -impl WithError for Wrapper +impl WithError for Wrapper where - A: WithError, - Ctx: ?Sized, + A: WithError, { type Error = A::Error; type Transformed = A::Transformed; @@ -65,9 +64,14 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out + where + Events: 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// @@ -85,19 +89,21 @@ pub trait Adapter { impl Adapter for A where - Events: Stream + 'static, + Events: Stream, Ctx: 'static + ?Sized, - A: WithError, + A: WithError, Wrapper: Transformer + 'static, - >::Transformed: + ::Transformed: From< as Transformer>::Transformed>, - >::Error: + ::Error: From< as Transformer>::Error>, { - type Error = >::Error; - type Transformed = >::Transformed; - type TransformedStream<'out> = - TransformedStream<'out, Wrapper, Events, Ctx>; + type Error = ::Error; + type Transformed = ::Transformed; + type TransformedStream<'out> + where + Events: 'out, + = TransformedStream<'out, Wrapper, Events, Ctx>; fn transform_all<'me, 'ctx, 'out>( &'me self, @@ -176,15 +182,15 @@ impl<'out, Adapter, Events, Ctx> Stream where Events: Stream, Ctx: ?Sized, - Adapter: Transformer + WithError, - >::Transformed: + Adapter: Transformer + WithError, + ::Transformed: From<>::Transformed>, - >::Error: + ::Error: From<>::Error>, { type Item = Result< - >::Transformed, - >::Error, + ::Transformed, + ::Error, >; fn poll_next( diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 82d8cbf..5a68ece 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -36,9 +36,12 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -65,5 +68,5 @@ where /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event - type Strategy: Strategy; + type Strategy; } diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs index 53fab34..6993d7a 100644 --- a/core/src/es/adapter/transformer/strategy.rs +++ b/core/src/es/adapter/transformer/strategy.rs @@ -1,8 +1,6 @@ //! [`Strategy`] definition and default implementations. -use std::{ - convert::Infallible, fmt::Debug, iter::Iterator, marker::PhantomData, -}; +use std::{iter::Iterator, marker::PhantomData}; use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; @@ -30,9 +28,12 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - #[rustfmt::skip] - type TransformedStream<'out>: - Stream> + 'out; + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -52,11 +53,11 @@ impl Transformer for adapter::Wrapper where Ctx: ?Sized, Event: event::Versioned, - Adapter: WithStrategy + adapter::WithError, + Adapter: WithStrategy + adapter::WithError, Adapter::Strategy: Strategy, - >::Transformed: + ::Transformed: From<>::Transformed>, - >::Error: + ::Error: From<>::Error>, { type Error = >::Error; @@ -136,7 +137,7 @@ impl Strategy for Skip where Ctx: ?Sized, Event: event::Versioned, - Adapter: adapter::WithError, + Adapter: adapter::WithError, Adapter::Transformed: 'static, Adapter::Error: 'static, { @@ -166,10 +167,12 @@ pub struct AsIs; impl Strategy for AsIs where + Adapter: adapter::WithError, + Adapter::Error: 'static, Ctx: ?Sized, Event: event::Versioned + 'static, { - type Error = Infallible; + type Error = Adapter::Error; type Transformed = Event; type TransformedStream<'out> = stream::Once>>; @@ -253,10 +256,11 @@ where Ctx: ?Sized, Event: event::Versioned, IntoEvent: 'static, - Adapter: Splitter, + Adapter: Splitter + adapter::WithError, Adapter::Iterator: 'static, + Adapter::Error: 'static, { - type Error = Infallible; + type Error = Adapter::Error; type Transformed = ::Item; type TransformedStream<'out> = SplitStream; @@ -279,6 +283,71 @@ type SplitStream = stream::Map< <>::Iterator as Iterator>::Item, ) -> Result< <>::Iterator as Iterator>::Item, - Infallible, + ::Error, >, >; + +/// TODO +#[derive(Clone, Copy, Debug)] +pub struct Custom; + +/// TODO +pub trait CustomTransformer +where + Event: event::Versioned, + Ctx: ?Sized, +{ + /// Error of this [`Strategy`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; + + /// Converts incoming [`Event`] into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform<'me, 'ctx, 'out>( + &'me self, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; +} + +impl Strategy for Custom +where + Adapter: CustomTransformer, + Event: event::Versioned, +{ + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'out> = Adapter::TransformedStream<'out>; + + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + Adapter::transform(adapter, event, context) + } +} diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 93a08cf..668d638 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -17,7 +17,7 @@ async fn main() { let mut email = Option::::None; let chat_events = storage::chat::Adapter - .transform_all(incoming_events(), &1) + .transform_all(incoming_events(), &()) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -51,7 +51,7 @@ async fn main() { ); let message_events = storage::message::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &1) .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 3418d2e..959c7a9 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -2,30 +2,29 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::strategy::{AsIs, Initialized, Into, Skip}, - Transformer, + transformer::{strategy, Strategy}, }; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Chat; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => ( +#[derive(Debug, Strategy)] +#[strategy( + strategy::Initialized => ( event::chat::public::Created, event::chat::private::Created, ), - AsIs => event::message::Posted, - Skip => ( + strategy::AsIs => event::message::Posted, + strategy::Skip => ( event::email::v1::AddedAndConfirmed, event::email::Confirmed, event::email::Added, ), - Initialized> => ( + strategy::Initialized> => ( event::chat::v1::Created, ), )] diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 90a1895..180bb97 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -3,8 +3,7 @@ use std::{array, convert::Infallible}; use arcana::es::{ adapter::{ self, - transformer::strategy::{AsIs, Initialized, Skip, Split, Splitter}, - Transformer, + transformer::{strategy, Strategy}, }, event::Initial, }; @@ -12,29 +11,29 @@ use either::Either; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Email; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => event::email::Added, - AsIs => event::email::Confirmed, - Skip => ( +#[derive(Debug, Strategy)] +#[strategy( + strategy::Initialized => event::email::Added, + strategy::AsIs => event::email::Confirmed, + strategy::Skip => ( event::chat::public::Created, event::chat::private::Created, event::chat::v1::Created, event::message::Posted, ), - Split> => ( + strategy::Split> => ( event::email::v1::AddedAndConfirmed, ), )] pub struct Adapter; impl - Splitter< + strategy::Splitter< event::email::v1::AddedAndConfirmed, Either, > for Adapter diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index bce2a66..174894e 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -2,29 +2,53 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::strategy::{Initialized, Skip}, - Transformer, + transformer::{strategy, Strategy}, }; +use futures::stream; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::WithError for Adapter { type Error = Infallible; type Transformed = event::Message; } -#[derive(Debug, Transformer)] -#[transformer( - Initialized => ( +#[derive(Debug, Strategy)] +#[strategy( + strategy::Initialized => ( event::message::Posted, ), - Skip => ( - event::chat::public::Created, + strategy::Skip => ( event::chat::private::Created, event::chat::v1::Created, event::email::Added, event::email::Confirmed, event::email::v1::AddedAndConfirmed, ), + strategy::Custom => event::chat::public::Created, )] pub struct Adapter; + +// Basically same as Skip, but with additional Ctx bounds +impl strategy::CustomTransformer + for Adapter +where + Ctx: From, +{ + type Error = Infallible; + type Transformed = event::Message; + type TransformedStream<'out> = + stream::Empty>; + + fn transform<'me, 'ctx, 'out>( + &'me self, + _event: event::chat::public::Created, + _context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + stream::empty() + } +} diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index 657ffc4..f14db38 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -7,7 +7,3 @@ pub use self::strategy::Strategy; #[doc(inline)] pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; - -#[cfg(feature = "derive")] -#[doc(inline)] -pub use arcana_codegen::es::transformer::Transformer; diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index 589a35a..a8af22a 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,5 +2,10 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Initialized, Into, Skip, Split, Splitter, Strategy, + AsIs, Custom, CustomTransformer, Initialized, Into, Skip, Split, Splitter, + Strategy, }; + +#[cfg(feature = "derive")] +#[doc(inline)] +pub use arcana_codegen::es::strategy::Strategy; From c58555c2359cf1e9db4fbccf7a722c43c3349cde Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Sep 2021 12:16:25 +0300 Subject: [PATCH 072/104] Fix Event derive macro spec [skip ci] --- codegen/impl/src/es/event/mod.rs | 305 +++++++++++++++- codegen/impl/src/es/event/strategy.rs | 472 ------------------------- codegen/shim/src/lib.rs | 8 - codegen/src/es/mod.rs | 1 - codegen/src/es/strategy.rs | 5 - examples/chat/src/storage/chat.rs | 54 ++- examples/chat/src/storage/email.rs | 52 ++- examples/chat/src/storage/message.rs | 51 ++- src/es/adapter/mod.rs | 3 +- src/es/adapter/transformer/strategy.rs | 4 - 10 files changed, 415 insertions(+), 540 deletions(-) delete mode 100644 codegen/impl/src/es/event/strategy.rs delete mode 100644 codegen/src/es/strategy.rs diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index dbf5a1e..96b92fb 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -1,6 +1,5 @@ //! `#[derive(Event)]` macro implementation. -pub mod strategy; pub mod versioned; use std::{convert::TryFrom, iter}; @@ -333,6 +332,10 @@ impl Definition { let (impl_gen, _, where_clause) = generics.split_for_impl(); let (_, type_gen, _) = self.generics.split_for_impl(); + let unreachable_arm = self + .has_ignored_variants + .then(|| quote! { _ => unreachable!(), }); + quote! { #[automatically_derived] impl#impl_gen ::arcana::es::adapter::Transformer< @@ -358,6 +361,7 @@ impl Definition { { match __event { #inner_match + #unreachable_arm } } } @@ -870,6 +874,161 @@ mod spec { } } + #[automatically_derived] + impl<'a, F, C, __A, __Ctx> ::arcana::es::adapter:: + Transformer, __Ctx> for + ::arcana::es::adapter::Wrapper<__A> + where + __A: ::arcana::es::adapter::WithError, + Self: + ::arcana::es::adapter::Transformer, __Ctx> + + ::arcana::es::adapter::Transformer, __Ctx>, + <__A as ::arcana::es::adapter::WithError>::Transformed: + ::std::convert::From< , __Ctx> >::Transformed> + + ::std::convert::From< , __Ctx> >::Transformed> + + 'static, + <__A as ::arcana::es::adapter::WithError>::Error: + ::std::convert::From< , __Ctx> >::Error> + + ::std::convert::From< , __Ctx> >::Error> + + 'static, + , __Ctx> >::Transformed: + 'static, + , __Ctx> >::Error: + 'static, + , __Ctx> >::Transformed: + 'static, + , __Ctx> >::Error: 'static + { + type Error = <__A as ::arcana::es::adapter::WithError>:: + Error; + type Transformed = <__A as ::arcana::es::adapter::WithError>:: + Transformed; + type TransformedStream<'out> = + ::arcana::es::adapter::codegen::futures::future::Either< + ::arcana::es::adapter::codegen::futures::stream::Map< + , __Ctx + >>::TransformedStream<'out>, + fn( + ::std::result::Result< + , __Ctx + >>::Transformed, + , __Ctx + >>::Error, + >, + ) -> ::std::result::Result< + , __Ctx>>:: + Transformed, + , __Ctx>>:: + Error, + > + >, + ::arcana::es::adapter::codegen::futures::stream::Map< + , __Ctx + >>::TransformedStream<'out>, + fn( + ::std::result::Result< + , __Ctx + >>::Transformed, + , __Ctx + >>::Error, + >, + ) -> ::std::result::Result< + , __Ctx + >>::Transformed, + , __Ctx + >>::Error, + > + >, + >; + + fn transform<'me, 'ctx, 'out>( + &'me self, + __event: Event<'a, F, C>, + __context: &'ctx __Ctx, + ) -> , __Ctx>>:: + TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + match __event { + Event::<'a, F, C>::File(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + , __Ctx + > >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + Event::<'a, F, C>::Chat(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + , __Ctx + > >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + } + } + } + #[automatically_derived] #[doc(hidden)] impl<'a, F, C> ::arcana::es::event::codegen::Versioned @@ -1008,6 +1167,150 @@ mod spec { } } + #[automatically_derived] + impl<__A, __Ctx> ::arcana::es::adapter::Transformer + for ::arcana::es::adapter::Wrapper<__A> + where + __A: ::arcana::es::adapter::WithError, + Self: + ::arcana::es::adapter::Transformer + + ::arcana::es::adapter::Transformer, + <__A as ::arcana::es::adapter::WithError>::Transformed: + ::std::convert::From< >::Transformed> + + ::std::convert::From< >::Transformed> + + 'static, + <__A as ::arcana::es::adapter::WithError>::Error: + ::std::convert::From< >::Error> + + ::std::convert::From< >::Error> + + 'static, + >::Transformed: 'static, + >::Error: 'static, + >::Transformed: 'static, + >::Error: 'static + { + type Error = <__A as ::arcana::es::adapter::WithError>:: + Error; + type Transformed = <__A as ::arcana::es::adapter::WithError>:: + Transformed; + type TransformedStream<'out> = + ::arcana::es::adapter::codegen::futures::future::Either< + ::arcana::es::adapter::codegen::futures::stream::Map< + >::TransformedStream<'out>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + > + >, + ::arcana::es::adapter::codegen::futures::stream::Map< + >::TransformedStream<'out>, + fn( + ::std::result::Result< + >:: + Transformed, + >:: + Error, + >, + ) -> ::std::result::Result< + >:: + Transformed, + >::Error, + > + >, + >; + + fn transform<'me, 'ctx, 'out>( + &'me self, + __event: Event, + __context: &'ctx __Ctx, + ) -> >:: + TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + match __event { + Event::File(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + left_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + Event::Chat(__event) => { + ::arcana::es::adapter::codegen::futures::StreamExt:: + right_stream( + ::arcana::es::adapter::codegen::futures:: + StreamExt::map( + + >::transform(self, __event, __context), + { + let __transform_fn: fn(_) -> _ = + |__res| { + ::std::result::Result::map_err( + ::std::result::Result::map( + __res, + ::std::convert::Into::into, + ), + ::std::convert::Into::into, + ) + }; + __transform_fn + }, + ) + ) + }, + _ => unreachable!(), + } + } + } + #[automatically_derived] #[doc(hidden)] impl ::arcana::es::event::codegen::Versioned for Event { diff --git a/codegen/impl/src/es/event/strategy.rs b/codegen/impl/src/es/event/strategy.rs deleted file mode 100644 index 8e2fb6b..0000000 --- a/codegen/impl/src/es/event/strategy.rs +++ /dev/null @@ -1,472 +0,0 @@ -//! `#[derive(adapter::Transformer)]` macro implementation. - -use std::{collections::HashMap, convert::TryFrom}; - -use itertools::Itertools as _; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, -}; -use synthez::{ParseAttrs, ToTokens}; - -/// Expands `#[derive(Strategy)]` macro. -/// -/// # Errors -/// -/// - If failed to parse [`Attrs`]. -pub fn derive(input: TokenStream) -> syn::Result { - let input = syn::parse2::(input)?; - let definition = Definition::try_from(input)?; - - Ok(quote! { #definition }) -} - -/// Helper attributes of `#[derive(Strategy)]` macro placed on an enum variant. -#[derive(Debug, Default, PartialEq)] -pub struct Attr { - /// [`Strategies`][0] with corresponding [`VersionedEvent`][1]s. - /// - /// [0]: arcana_core::es::adapter::transformer::Strategy - /// [1]: arcana_core::es::VersionedEvent - pub strategies: HashMap>, -} - -impl Parse for Attr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let parse_attr = |input: ParseStream<'_>| { - let parenthesized = || { - let content; - let _ = syn::parenthesized!(content in input); - Ok(content) - }; - let events = || { - parenthesized().map_or_else( - |_| input.parse().map(|ty| vec![ty]), - |par| { - par.parse_terminated::<_, syn::Token![,]>( - syn::Type::parse, - ) - .map(|ty| ty.into_iter().collect::>()) - }, - ) - }; - - let strategy = input.parse()?; - let _ = input.parse::()?; - let _ = input.parse::]>()?; - - Ok((strategy, events()?)) - }; - - let strategies = input - .parse_terminated::<_, syn::Token![,]>(parse_attr)? - .into_iter() - .collect::>(); - - Ok(Self { strategies }) - } -} - -impl ParseAttrs for Attr { - fn try_merge(self, another: Self) -> syn::Result { - Ok(Self { - strategies: self - .strategies - .into_iter() - .chain(another.strategies.into_iter()) - .collect(), - }) - } -} - -/// Representation of a enum for implementing [`Transformer`][0], used for code -/// generation. -/// -/// [0]: arcana_core::es::adapter::Transformer -#[derive(Debug, ToTokens)] -#[to_tokens(append(impl_strategies))] -pub struct Definition { - /// Generic parameter of the [`Transformer`][0]. - /// - /// [0]: arcana_core::es::adapter::Transformer - pub adapter: syn::Ident, - - /// [`syn::Generics`] of this enum's type. - pub generics: syn::Generics, - - /// [`Strategies`][0] with corresponding [`VersionedEvent`][1]s. - /// - /// [0]: arcana_core::es::adapter::transformer::Strategy - /// [1]: arcana_core::es::VersionedEvent - pub strategies: HashMap>, -} - -impl TryFrom for Definition { - type Error = syn::Error; - - fn try_from(input: syn::DeriveInput) -> syn::Result { - let attrs: Attr = Attr::parse_attrs("strategy", &input)?; - - Ok(Self { - adapter: input.ident, - generics: input.generics, - strategies: attrs.strategies, - }) - } -} - -impl Definition { - /// Generates code to derive [`Strategy`][0] traits. - /// - /// [0]: arcana_core::es::adapter::transformer::Strategy - #[must_use] - pub fn impl_strategies(&self) -> TokenStream { - let transformed_and_err_bounds: Punctuated< - syn::WherePredicate, - syn::Token![,], - > = parse_quote! { - Self: ::arcana::es::adapter::WithError, - ::Transformed: 'static, - ::Error: 'static, - }; - - let mut generics = self.generics.clone(); - generics.params.push(parse_quote! { __Ctx }); - generics - .make_where_clause() - .predicates - .extend(transformed_and_err_bounds); - - let (impl_gen, _, where_cl) = generics.split_for_impl(); - let (_, type_gen, _) = self.generics.split_for_impl(); - let adapter = &self.adapter; - - self.strategies - .iter() - .sorted_by_key(|(s, _)| s.to_token_stream().to_string()) - .map(|(strategy, ev)| { - quote! { - #( impl#impl_gen ::arcana::es::adapter::transformer:: - WithStrategy<#ev, __Ctx> for #adapter#type_gen #where_cl - { - type Strategy = #strategy; - } )* - } - }) - .collect() - } -} - -#[cfg(test)] -mod spec { - use quote::quote; - use syn::parse_quote; - - #[allow(clippy::too_many_lines)] - #[test] - fn derives_enum_impl() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - enum Event { - File(FileEvent), - Chat(ChatEvent), - } - }; - - let output = quote! { - #[automatically_derived] - impl ::arcana::es::adapter::Transformer for Adapter { - type Context = dyn Any; - type Error = Infallible; - type Transformed = IntoEvent; - type TransformedStream<'me, 'ctx> = - ::arcana::es::adapter::codegen::futures::future::Either< - ::arcana::es::adapter::codegen::futures::stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - >:: - Transformed, - >:: - Error, - >, - ) -> ::std::result::Result< - >:: - Transformed, - >::Error, - >, - >, - ::arcana::es::adapter::codegen::futures::stream::Map< - >::TransformedStream<'me, 'ctx>, - fn( - ::std::result::Result< - >:: - Transformed, - >:: - Error, - >, - ) -> ::std::result::Result< - >:: - Transformed, - >::Error, - >, - >, - >; - - fn transform<'me, 'ctx>( - &'me self, - __event: Event, - __context: - &'ctx >::Context, - ) -> >:: - TransformedStream<'me, 'ctx> - { - match __event { - Event::File(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: - left_stream( - ::arcana::es::adapter::codegen::futures:: - StreamExt::map( - - >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = - |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) - ) - }, - Event::Chat(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: - right_stream( - ::arcana::es::adapter::codegen::futures:: - StreamExt::map( - - >::transform(self, __event, __context), - { - let __transform_fn: fn(_) -> _ = - |__res| { - ::std::result::Result::map_err( - ::std::result::Result::map( - __res, - ::std::convert::Into::into, - ), - ::std::convert::Into::into, - ) - }; - __transform_fn - }, - ) - ) - }, - } - } - } - }; - - assert_eq!( - super::derive(input).unwrap().to_string(), - output.to_string(), - ); - } - - #[test] - fn errors_on_without_adapter_attribute() { - let input = parse_quote! { - #[event( - transformer( - transformed = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - enum Event { - Event1(Event1), - Event2(Event2), - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - err.to_string(), - "`adapter` argument of `#[event(transformer)]` attribute is \ - expected to be present, but is absent", - ); - } - - #[test] - fn errors_on_without_transformed_attribute() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - context = dyn Any, - error = Infallible, - ), - )] - enum Event { - Event1(Event1), - Event2(Event2), - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - err.to_string(), - "either `into` or `transformed` argument of \ - `#[event(transformer)]` attribute is expected to be present, \ - but is absent", - ); - } - - #[test] - fn errors_on_without_context_attribute() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - transformed = IntoEvent, - error = Infallible, - ), - )] - enum Event { - Event1(Event1), - Event2(Event2), - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - err.to_string(), - "either `context` or `ctx` argument of \ - `#[event(transformer)]` attribute is expected to be present, \ - but is absent", - ); - } - - #[test] - fn errors_on_without_error_attribute() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - transformed = IntoEvent, - ctx = dyn Any, - ), - )] - enum Event { - Event1(Event1), - Event2(Event2), - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!( - err.to_string(), - "either `err` or `error` argument of \ - `#[event(transformer)]` attribute is expected to be present, \ - but is absent", - ); - } - - #[test] - fn errors_on_multiple_fields_in_variant() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - enum Event { - Event1(Event1), - Event2 { - event: Event2, - second_field: Event3, - } - } - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!(err.to_string(), "enum variants must have exactly 1 field"); - } - - #[test] - fn errors_on_struct() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - struct Event; - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!(err.to_string(), "expected enum only"); - } - - #[test] - fn errors_on_empty_enum() { - let input = parse_quote! { - #[event( - transformer( - adapter = Adapter, - into = IntoEvent, - context = dyn Any, - error = Infallible, - ), - )] - enum Event {} - }; - - let err = super::derive(input).unwrap_err(); - - assert_eq!(err.to_string(), "enum must have at least one variant"); - } -} diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 24abb82..200525d 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -158,11 +158,3 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } - -/// TODO -#[proc_macro_derive(Strategy, attributes(strategy))] -pub fn derive_strategy(input: TokenStream) -> TokenStream { - codegen::es::event::strategy::derive(input.into()) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} diff --git a/codegen/src/es/mod.rs b/codegen/src/es/mod.rs index 71739cb..0234461 100644 --- a/codegen/src/es/mod.rs +++ b/codegen/src/es/mod.rs @@ -3,4 +3,3 @@ //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html pub mod event; -pub mod strategy; diff --git a/codegen/src/es/strategy.rs b/codegen/src/es/strategy.rs deleted file mode 100644 index 7325603..0000000 --- a/codegen/src/es/strategy.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! [`Transformer`] derive macro. -//! -//! [`Transformer`]: arcana_core::es::adapter::Transformer - -pub use arcana_codegen_shim::Strategy; diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 959c7a9..208d11c 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::{strategy, Strategy}, + transformer::{self, strategy}, }; use crate::event; @@ -12,24 +12,44 @@ impl adapter::WithError for Adapter { type Transformed = event::Chat; } -#[derive(Debug, Strategy)] -#[strategy( - strategy::Initialized => ( - event::chat::public::Created, - event::chat::private::Created, - ), - strategy::AsIs => event::message::Posted, - strategy::Skip => ( - event::email::v1::AddedAndConfirmed, - event::email::Confirmed, - event::email::Added, - ), - strategy::Initialized> => ( - event::chat::v1::Created, - ), -)] +#[derive(Clone, Copy, Debug)] pub struct Adapter; +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = + strategy::Initialized>; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::AsIs; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 180bb97..ea7a3d9 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -3,7 +3,7 @@ use std::{array, convert::Infallible}; use arcana::es::{ adapter::{ self, - transformer::{strategy, Strategy}, + transformer::{self, strategy}, }, event::Initial, }; @@ -16,22 +16,44 @@ impl adapter::WithError for Adapter { type Transformed = event::Email; } -#[derive(Debug, Strategy)] -#[strategy( - strategy::Initialized => event::email::Added, - strategy::AsIs => event::email::Confirmed, - strategy::Skip => ( - event::chat::public::Created, - event::chat::private::Created, - event::chat::v1::Created, - event::message::Posted, - ), - strategy::Split> => ( - event::email::v1::AddedAndConfirmed, - ), -)] +#[derive(Clone, Copy, Debug)] pub struct Adapter; +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = + strategy::Split>; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + impl strategy::Splitter< event::email::v1::AddedAndConfirmed, diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 174894e..4034911 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; use arcana::es::adapter::{ self, - transformer::{strategy, Strategy}, + transformer::{self, strategy}, }; use futures::stream; @@ -13,22 +13,43 @@ impl adapter::WithError for Adapter { type Transformed = event::Message; } -#[derive(Debug, Strategy)] -#[strategy( - strategy::Initialized => ( - event::message::Posted, - ), - strategy::Skip => ( - event::chat::private::Created, - event::chat::v1::Created, - event::email::Added, - event::email::Confirmed, - event::email::v1::AddedAndConfirmed, - ), - strategy::Custom => event::chat::public::Created, -)] +#[derive(Debug)] pub struct Adapter; +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Initialized; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Custom; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy + for Adapter +{ + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + +impl transformer::WithStrategy for Adapter { + type Strategy = strategy::Skip; +} + // Basically same as Skip, but with additional Ctx bounds impl strategy::CustomTransformer for Adapter diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 97fce82..c9616f2 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,8 +7,7 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{ - transformer::strategy::{And, Any}, - Adapter, Correct, Returning, Wrapper, + Adapter, TransformedStream, WithError, Wrapper, }; #[cfg(feature = "derive")] diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index a8af22a..1aea266 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -5,7 +5,3 @@ pub use arcana_core::es::adapter::transformer::strategy::{ AsIs, Custom, CustomTransformer, Initialized, Into, Skip, Split, Splitter, Strategy, }; - -#[cfg(feature = "derive")] -#[doc(inline)] -pub use arcana_codegen::es::strategy::Strategy; From f4a9a4d6358b1e088f1401851ba7d1c5d0c0859e Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Sep 2021 16:23:52 +0300 Subject: [PATCH 073/104] WIP [skip ci] --- codegen/impl/src/es/event/mod.rs | 92 ++--- codegen/shim/src/lib.rs | 4 + core/Cargo.toml | 1 + core/src/es/adapter/mod.rs | 167 +++++++-- core/src/es/adapter/transformer/mod.rs | 41 +- core/src/es/adapter/transformer/strategy.rs | 353 ------------------ .../es/adapter/transformer/strategy/as_is.rs | 38 ++ .../es/adapter/transformer/strategy/custom.rs | 76 ++++ .../transformer/strategy/initialized.rs | 47 +++ .../es/adapter/transformer/strategy/into.rs | 47 +++ .../es/adapter/transformer/strategy/mod.rs | 100 +++++ .../es/adapter/transformer/strategy/skip.rs | 39 ++ .../es/adapter/transformer/strategy/split.rs | 68 ++++ core/src/es/event.rs | 6 - examples/chat/src/main.rs | 16 +- examples/chat/src/storage/chat.rs | 27 +- examples/chat/src/storage/email.rs | 27 +- examples/chat/src/storage/message.rs | 30 +- src/es/adapter/mod.rs | 7 +- src/es/adapter/transformer/strategy.rs | 3 +- 20 files changed, 657 insertions(+), 532 deletions(-) delete mode 100644 core/src/es/adapter/transformer/strategy.rs create mode 100644 core/src/es/adapter/transformer/strategy/as_is.rs create mode 100644 core/src/es/adapter/transformer/strategy/custom.rs create mode 100644 core/src/es/adapter/transformer/strategy/initialized.rs create mode 100644 core/src/es/adapter/transformer/strategy/into.rs create mode 100644 core/src/es/adapter/transformer/strategy/mod.rs create mode 100644 core/src/es/adapter/transformer/strategy/skip.rs create mode 100644 core/src/es/adapter/transformer/strategy/split.rs diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 96b92fb..df058a6 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -342,9 +342,9 @@ impl Definition { #event#type_gen, __Ctx > for ::arcana::es::adapter::Wrapper<__A> #where_clause { - type Error = <__A as ::arcana::es::adapter::WithError>:: + type Error = <__A as ::arcana::es::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::WithError>:: + type Transformed = <__A as ::arcana::es::adapter::Returning>:: Transformed; type TransformedStream<'out> = #transformed; @@ -389,13 +389,13 @@ impl Definition { syn::WherePredicate, syn::Token![,], > = parse_quote! { - __A: ::arcana::es::adapter::WithError, + __A: ::arcana::es::adapter::Returning, Self: #( ::arcana::es::adapter::Transformer<#var_type, __Ctx> )+*, - <__A as ::arcana::es::adapter::WithError>::Transformed: + <__A as ::arcana::es::adapter::Returning>::Transformed: #( ::std::convert::From<>::Transformed> +)* 'static, - <__A as ::arcana::es::adapter::WithError>::Error: + <__A as ::arcana::es::adapter::Returning>::Error: #( ::std::convert::From<>::Error> +)* 'static, @@ -434,7 +434,7 @@ impl Definition { let transformed_stream = |from: &syn::Type| { quote! { - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, @@ -462,7 +462,7 @@ impl Definition { Some( acc.map(|acc| { quote! { - ::arcana::es::adapter::codegen::futures::future:: + ::arcana::es::event::codegen::futures::future:: Either< #variant_stream, #acc, @@ -501,7 +501,7 @@ impl Definition { .enumerate() .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt::map( + ::arcana::es::event::codegen::futures::StreamExt::map( >::transform(self, __event, __context), @@ -521,11 +521,11 @@ impl Definition { }; let right_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: right_stream }; let left_stream = quote! { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: left_stream }; let left_stream_count = @@ -611,17 +611,17 @@ mod spec { impl<__A, __Ctx> ::arcana::es::adapter::Transformer for ::arcana::es::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::WithError, + __A: ::arcana::es::adapter::Returning, Self: ::arcana::es::adapter::Transformer + ::arcana::es::adapter::Transformer, - <__A as ::arcana::es::adapter::WithError>::Transformed: + <__A as ::arcana::es::adapter::Returning>::Transformed: ::std::convert::From< >::Transformed> + ::std::convert::From< >::Transformed> + 'static, - <__A as ::arcana::es::adapter::WithError>::Error: + <__A as ::arcana::es::adapter::Returning>::Error: ::std::convert::From< >::Error> + ::std::convert::From< >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::WithError>:: + type Error = <__A as ::arcana::es::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::WithError>:: + type Transformed = <__A as ::arcana::es::adapter::Returning>:: Transformed; type TransformedStream<'out> = - ::arcana::es::adapter::codegen::futures::future::Either< - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::future::Either< + ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, @@ -663,7 +663,7 @@ mod spec { Transformer>::Error, > >, - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, @@ -699,9 +699,9 @@ mod spec { { match __event { Event::File(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: left_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( @@ -723,9 +723,9 @@ mod spec { ) }, Event::Chat(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: right_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( @@ -879,17 +879,17 @@ mod spec { Transformer, __Ctx> for ::arcana::es::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::WithError, + __A: ::arcana::es::adapter::Returning, Self: ::arcana::es::adapter::Transformer, __Ctx> + ::arcana::es::adapter::Transformer, __Ctx>, - <__A as ::arcana::es::adapter::WithError>::Transformed: + <__A as ::arcana::es::adapter::Returning>::Transformed: ::std::convert::From< , __Ctx> >::Transformed> + ::std::convert::From< , __Ctx> >::Transformed> + 'static, - <__A as ::arcana::es::adapter::WithError>::Error: + <__A as ::arcana::es::adapter::Returning>::Error: ::std::convert::From< , __Ctx> >::Error> + ::std::convert::From< , __Ctx> >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::WithError>:: + type Error = <__A as ::arcana::es::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::WithError>:: + type Transformed = <__A as ::arcana::es::adapter::Returning>:: Transformed; type TransformedStream<'out> = - ::arcana::es::adapter::codegen::futures::future::Either< - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::future::Either< + ::arcana::es::event::codegen::futures::stream::Map< , __Ctx >>::TransformedStream<'out>, @@ -937,7 +937,7 @@ mod spec { Error, > >, - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::stream::Map< , __Ctx >>::TransformedStream<'out>, @@ -978,9 +978,9 @@ mod spec { { match __event { Event::<'a, F, C>::File(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: left_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( , __Ctx @@ -1002,9 +1002,9 @@ mod spec { ) }, Event::<'a, F, C>::Chat(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: right_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( , __Ctx @@ -1171,17 +1171,17 @@ mod spec { impl<__A, __Ctx> ::arcana::es::adapter::Transformer for ::arcana::es::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::WithError, + __A: ::arcana::es::adapter::Returning, Self: ::arcana::es::adapter::Transformer + ::arcana::es::adapter::Transformer, - <__A as ::arcana::es::adapter::WithError>::Transformed: + <__A as ::arcana::es::adapter::Returning>::Transformed: ::std::convert::From< >::Transformed> + ::std::convert::From< >::Transformed> + 'static, - <__A as ::arcana::es::adapter::WithError>::Error: + <__A as ::arcana::es::adapter::Returning>::Error: ::std::convert::From< >::Error> + ::std::convert::From< >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::WithError>:: + type Error = <__A as ::arcana::es::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::WithError>:: + type Transformed = <__A as ::arcana::es::adapter::Returning>:: Transformed; type TransformedStream<'out> = - ::arcana::es::adapter::codegen::futures::future::Either< - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::future::Either< + ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, @@ -1223,7 +1223,7 @@ mod spec { Transformer>::Error, > >, - ::arcana::es::adapter::codegen::futures::stream::Map< + ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, @@ -1259,9 +1259,9 @@ mod spec { { match __event { Event::File(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: left_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( @@ -1283,9 +1283,9 @@ mod spec { ) }, Event::Chat(__event) => { - ::arcana::es::adapter::codegen::futures::StreamExt:: + ::arcana::es::event::codegen::futures::StreamExt:: right_stream( - ::arcana::es::adapter::codegen::futures:: + ::arcana::es::event::codegen::futures:: StreamExt::map( diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 200525d..adc6518 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -60,6 +60,8 @@ use proc_macro::TokenStream; /// # Example /// /// ```rust,compile_fail,E0080 +/// # #![feature(generic_associated_types)] +/// # /// # use arcana::es::{event, Event}; /// # /// #[derive(event::Versioned)] @@ -80,6 +82,8 @@ use proc_macro::TokenStream; /// ``` /// /// ```rust +/// # #![feature(generic_associated_types)] +/// # /// # use arcana::es::{event, Event}; /// # /// # #[derive(event::Versioned)] diff --git a/core/Cargo.toml b/core/Cargo.toml index b402409..bfdb907 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,6 +26,7 @@ sealed = { version = "0.3", optional = true } [dev-dependencies] arcana = { version = "0.1.0-dev", path = "..", features = ["derive", "es"] } +futures = { version = "0.3", features = ["executor"] } [package.metadata.docs.rs] all-features = true diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index f79f987..5f45e31 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -13,30 +13,19 @@ use pin_project::pin_project; use ref_cast::RefCast; #[doc(inline)] -pub use self::transformer::Transformer; +pub use self::transformer::{strategy, Strategy, Transformer, WithStrategy}; -/// TODO -pub trait WithError { - /// TODO +/// Specifies result of [`Adapter`]. +pub trait Returning { + /// Error of this [`Adapter`]. type Error; - /// TODO + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event type Transformed; } -/// TODO -#[derive(Debug, RefCast)] -#[repr(transparent)] -pub struct Wrapper(pub A); - -impl WithError for Wrapper -where - A: WithError, -{ - type Error = A::Error; - type Transformed = A::Transformed; -} - /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -44,13 +33,106 @@ where /// - Transforming (ex: from one [`Version`] to another); /// - [`Split`]ting existing [`Event`]s into more granular ones. /// -/// Provided with blanket impl for [`Transformer`] implementors, so usually you -/// shouldn't implement it manually. +/// Usually provided as blanket impl, so you shouldn't implement it manually. +/// For that you'll need to implement [`Returning`] to specify transformation +/// result and [`WithStrategy`] for every [`VersionedEvent`] which is part of +/// transformed [`Event`]. And as long as [`Event`] is implemented via derive +/// macro you should be good to go. +/// +/// # Example +/// +/// ```rust +/// # #![feature(generic_associated_types)] +/// # +/// # use std::convert::Infallible; +/// # +/// # use arcana::es::{ +/// # adapter::{self, strategy}, +/// # Event, EventAdapter as _, VersionedEvent, +/// # }; +/// # use derive_more::From; +/// # use futures::{stream, TryStreamExt as _}; +/// # +/// #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// #[event(name = "chat", version = 1)] +/// struct ChatEvent; +/// +/// #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// #[event(name = "file", version = 2)] +/// struct FileEvent; +/// +/// // Some outdated Event. +/// #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// #[event(name = "file", version = 1)] +/// struct FileEventV1; +/// +/// // Repository-level Event, which is loaded from some Event Store and +/// // includes legacy Events. +/// #[derive(Clone, Copy, Debug, Event, PartialEq, From)] +/// enum RepositoryEvent { +/// FileV1(FileEventV1), +/// File(FileEvent), +/// Chat(ChatEvent), +/// } +/// +/// // Actual Event we want to transform RepositoryEvent into +/// #[derive(Clone, Copy, Debug, Event, From, PartialEq)] +/// enum FileDomainEvent { +/// File(FileEvent), +/// } +/// +/// #[derive(Clone, Copy)] +/// struct Adapter; +/// +/// impl adapter::Returning for Adapter { +/// type Error = Infallible; +/// type Transformed = FileDomainEvent; +/// } /// +/// impl adapter::WithStrategy for Adapter { +/// type Strategy = strategy::AsIs; +/// } +/// +/// impl adapter::WithStrategy for Adapter { +/// type Strategy = strategy::Into; +/// } +/// +/// impl adapter::WithStrategy for Adapter { +/// type Strategy = strategy::Skip; +/// } +/// +/// # let assertion = async { +/// let events = stream::iter::<[RepositoryEvent; 3]>([ +/// FileEventV1.into(), +/// FileEvent.into(), +/// ChatEvent.into(), +/// ]); +/// +/// let transformed = Adapter +/// .transform_all(events, &()) +/// .try_collect::>() +/// .await +/// .unwrap(); +/// +/// assert_eq!(transformed, vec![FileEvent.into(), FileEvent.into()]); +/// # }; +/// # +/// # futures::executor::block_on(assertion); +/// # +/// # impl From for FileEvent { +/// # fn from(_: FileEventV1) -> Self { +/// # Self +/// # } +/// # } +/// ``` +/// +/// [`Error`]: Self::Error /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split +/// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version +/// [`VersionedEvent`]: crate::es::VersionedEvent pub trait Adapter { /// Error of this [`Adapter`]. type Error; @@ -91,15 +173,15 @@ impl Adapter for A where Events: Stream, Ctx: 'static + ?Sized, - A: WithError, + A: Returning, Wrapper: Transformer + 'static, - ::Transformed: + ::Transformed: From< as Transformer>::Transformed>, - ::Error: + ::Error: From< as Transformer>::Error>, { - type Error = ::Error; - type Transformed = ::Transformed; + type Error = ::Error; + type Transformed = ::Transformed; type TransformedStream<'out> where Events: 'out, @@ -118,6 +200,22 @@ where } } +/// Wrapper type for [`Adapter`] to satisfy orphan rules on [`Event`] derive +/// macro. Shouldn't be used manually. +/// +/// [`Event`]: crate::es::Event +#[derive(Debug, RefCast)] +#[repr(transparent)] +pub struct Wrapper(pub A); + +impl Returning for Wrapper +where + A: Returning, +{ + type Error = A::Error; + type Transformed = A::Transformed; +} + #[pin_project] /// [`Stream`] for [`Adapter`] blanket impl. pub struct TransformedStream<'out, Adapter, Events, Ctx> @@ -182,15 +280,15 @@ impl<'out, Adapter, Events, Ctx> Stream where Events: Stream, Ctx: ?Sized, - Adapter: Transformer + WithError, - ::Transformed: + Adapter: Transformer + Returning, + ::Transformed: From<>::Transformed>, - ::Error: + ::Error: From<>::Error>, { type Item = Result< - ::Transformed, - ::Error, + ::Transformed, + ::Error, >; fn poll_next( @@ -219,12 +317,3 @@ where } } } - -#[cfg(feature = "codegen")] -pub mod codegen { - //! Re-exports for [`Transformer`] derive macro. - //! - //! [`Transformer`]: crate::es::adapter::Transformer - - pub use futures; -} diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 5a68ece..313d97d 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -4,11 +4,27 @@ pub mod strategy; use futures::Stream; -use crate::es::event; - #[doc(inline)] pub use strategy::Strategy; +/// To use [`Adapter`] with some [`Event`], you should provide [`Strategy`] +/// for every [`VersionedEvent`] involved with this [`Event`] and implement +/// [`Returning`] on [`Adapter`] itself. +/// +/// [`Adapter`]: crate::es::Adapter +/// [`Event`]: crate::es::Event +/// [`Returning`]: super::Returning +/// [`VersionedEvent`]: crate::es::VersionedEvent +pub trait WithStrategy +where + Self: Sized, +{ + /// [`Strategy`] to transform [`Event`] with. + /// + /// [`Event`]: crate::es::Event + type Strategy; +} + /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -16,9 +32,12 @@ pub use strategy::Strategy; /// - Transforming (ex: from one [`Version`] to another); /// - [`Split`]ting existing [`Event`]s into more granular ones. /// -/// To reduce boilerplate consider using [`WithStrategy`] with some [`Strategy`] -/// instead of implementing this trait manually. +/// Provided with blanket impl for [`WithStrategy`] implementors, so usually you +/// shouldn't implement it manually. For more flexibility consider using +/// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. +/// See [`Adapter`] for more info. /// +/// [`Custom`]: strategy::Custom /// [`Event`]: crate::es::Event /// [`Skip`]: strategy::Skip /// [`Split`]: strategy::Split @@ -56,17 +75,3 @@ pub trait Transformer { 'me: 'out, 'ctx: 'out; } - -/// Instead of implementing [`Transformer`] manually, you can use this trait -/// with some [`Strategy`]. -pub trait WithStrategy -where - Self: Sized, - Event: event::Versioned, - Ctx: ?Sized, -{ - /// [`Strategy`] to transform [`Event`] with. - /// - /// [`Event`]: crate::es::Event - type Strategy; -} diff --git a/core/src/es/adapter/transformer/strategy.rs b/core/src/es/adapter/transformer/strategy.rs deleted file mode 100644 index 6993d7a..0000000 --- a/core/src/es/adapter/transformer/strategy.rs +++ /dev/null @@ -1,353 +0,0 @@ -//! [`Strategy`] definition and default implementations. - -use std::{iter::Iterator, marker::PhantomData}; - -use futures::{future, stream, Stream, StreamExt as _, TryStreamExt as _}; - -use crate::es::{adapter, event}; - -use super::{Transformer, WithStrategy}; - -/// Generalized [`Transformer`] for [`Versioned`] events. -/// -/// [`Versioned`]: event::Versioned -pub trait Strategy -where - Event: event::Versioned, - Ctx: ?Sized, -{ - /// Error of this [`Strategy`]. - type Error; - - /// Converted [`Event`]. - /// - /// [`Event`]: crate::es::Event - type Transformed; - - /// [`Stream`] of [`Transformed`] [`Event`]s. - /// - /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed - type TransformedStream<'out>: Stream< - Item = Result< - >::Transformed, - >::Error, - >, - > + 'out; - - /// Converts incoming [`Event`] into [`Transformed`]. - /// - /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out; -} - -impl Transformer for adapter::Wrapper -where - Ctx: ?Sized, - Event: event::Versioned, - Adapter: WithStrategy + adapter::WithError, - Adapter::Strategy: Strategy, - ::Transformed: - From<>::Transformed>, - ::Error: - From<>::Error>, -{ - type Error = >::Error; - type Transformed = - >::Transformed; - type TransformedStream<'out> = >::TransformedStream<'out>; - - fn transform<'me, 'ctx, 'out>( - &'me self, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - >::transform( - &self.0, event, context, - ) - } -} - -/// [`Strategy`] for wrapping [`Event`]s in [`Initial`]. -/// -/// [`Event`]: crate::es::Event -/// [`Initial`]: event::Initial -#[derive(Clone, Debug)] -pub struct Initialized(PhantomData); - -impl Strategy - for Initialized -where - Ctx: ?Sized, - Event: event::Versioned, - InnerStrategy: Strategy, - InnerStrategy::Transformed: 'static, - InnerStrategy::Error: 'static, -{ - type Error = InnerStrategy::Error; - type Transformed = event::Initial; - type TransformedStream<'out> = stream::MapOk< - InnerStrategy::TransformedStream<'out>, - WrapInitial, - >; - - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) - } -} - -type WrapInitial = fn(Event) -> event::Initial; - -/// [`Strategy`] for skipping [`Event`]s. -/// -/// Until [never] is stabilized, [`Adapter::Transformed`] must implement -/// [`From`] [`Unknown`]. -/// -/// [never]: https://doc.rust-lang.org/stable/std/primitive.never.html -/// [`Adapter::Transformed`]: crate::es::Adapter::Transformed -/// [`Event`]: crate::es::Event -#[derive(Clone, Copy, Debug)] -pub struct Skip; - -impl Strategy for Skip -where - Ctx: ?Sized, - Event: event::Versioned, - Adapter: adapter::WithError, - Adapter::Transformed: 'static, - Adapter::Error: 'static, -{ - type Error = Adapter::Error; - type Transformed = Adapter::Transformed; - type TransformedStream<'out> = - stream::Empty>; - - fn transform<'me, 'ctx, 'out>( - _: &'me Adapter, - _: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - stream::empty() - } -} - -/// [`Strategy`] for passing [`Event`]s as is, without any conversions. -/// -/// [`Event`]: crate::es::Event -#[derive(Clone, Copy, Debug)] -pub struct AsIs; - -impl Strategy for AsIs -where - Adapter: adapter::WithError, - Adapter::Error: 'static, - Ctx: ?Sized, - Event: event::Versioned + 'static, -{ - type Error = Adapter::Error; - type Transformed = Event; - type TransformedStream<'out> = - stream::Once>>; - - fn transform<'me, 'ctx, 'out>( - _: &'me Adapter, - event: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - stream::once(future::ready(Ok(event))) - } -} - -/// [`Strategy`] for converting [`Event`]s using [`From`] impl. -/// -/// [`Event`]: crate::es::Event -#[derive(Copy, Clone, Debug)] -pub struct Into(PhantomData<(I, InnerStrategy)>); - -impl - Strategy for Into -where - Ctx: ?Sized, - Event: event::Versioned, - InnerStrategy: Strategy, - InnerStrategy::Transformed: 'static, - InnerStrategy::Error: 'static, - IntoEvent: From + 'static, -{ - type Error = InnerStrategy::Error; - type Transformed = IntoEvent; - type TransformedStream<'out> = stream::MapOk< - InnerStrategy::TransformedStream<'out>, - IntoFn, - >; - - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - ctx: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) - } -} - -type IntoFn = fn(FromEvent) -> IntoEvent; - -/// [`Strategy`] for splitting single [`Event`] into multiple. Implement -/// [`Splitter`] to define splitting logic. -/// -/// [`Event`]: crate::es::Event -#[derive(Clone, Copy, Debug)] -pub struct Split(PhantomData); - -/// Split single [`Event`] into multiple for [`Split`] [`Strategy`]. -/// -/// [`Event`]: crate::es::Event -pub trait Splitter { - /// [`Iterator`] of split [`Event`]s. - /// - /// [`Event`]: crate::es::Event - type Iterator: Iterator; - - /// Splits [`Event`]. - /// - /// [`Event`]: crate::es::Event - fn split(&self, event: From) -> Self::Iterator; -} - -impl Strategy - for Split -where - Ctx: ?Sized, - Event: event::Versioned, - IntoEvent: 'static, - Adapter: Splitter + adapter::WithError, - Adapter::Iterator: 'static, - Adapter::Error: 'static, -{ - type Error = Adapter::Error; - type Transformed = ::Item; - type TransformedStream<'out> = SplitStream; - - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - stream::iter(adapter.split(event)).map(Ok) - } -} - -type SplitStream = stream::Map< - stream::Iter<>::Iterator>, - fn( - <>::Iterator as Iterator>::Item, - ) -> Result< - <>::Iterator as Iterator>::Item, - ::Error, - >, ->; - -/// TODO -#[derive(Clone, Copy, Debug)] -pub struct Custom; - -/// TODO -pub trait CustomTransformer -where - Event: event::Versioned, - Ctx: ?Sized, -{ - /// Error of this [`Strategy`]. - type Error; - - /// Converted [`Event`]. - /// - /// [`Event`]: crate::es::Event - type Transformed; - - /// [`Stream`] of [`Transformed`] [`Event`]s. - /// - /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed - type TransformedStream<'out>: Stream< - Item = Result< - >::Transformed, - >::Error, - >, - > + 'out; - - /// Converts incoming [`Event`] into [`Transformed`]. - /// - /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( - &'me self, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out; -} - -impl Strategy for Custom -where - Adapter: CustomTransformer, - Event: event::Versioned, -{ - type Error = Adapter::Error; - type Transformed = Adapter::Transformed; - type TransformedStream<'out> = Adapter::TransformedStream<'out>; - - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - Adapter::transform(adapter, event, context) - } -} diff --git a/core/src/es/adapter/transformer/strategy/as_is.rs b/core/src/es/adapter/transformer/strategy/as_is.rs new file mode 100644 index 0000000..8a8be5e --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/as_is.rs @@ -0,0 +1,38 @@ +//! [`AsIs`] [`Strategy`] definition. + +use futures::{future, stream}; + +use crate::es::{adapter, event}; + +use super::Strategy; + +/// [`Strategy`] for passing [`Event`]s as is, without any conversions. +/// +/// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] +pub struct AsIs; + +impl Strategy for AsIs +where + Adapter: adapter::Returning, + Adapter::Error: 'static, + Ctx: ?Sized, + Event: event::Versioned + 'static, +{ + type Error = Adapter::Error; + type Transformed = Event; + type TransformedStream<'out> = + stream::Once>>; + + fn transform<'me, 'ctx, 'out>( + _: &'me Adapter, + event: Event, + _: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + stream::once(future::ready(Ok(event))) + } +} diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs new file mode 100644 index 0000000..ffc3d57 --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -0,0 +1,76 @@ +//! [`Custom`] [`Strategy`] definition. + +use futures::Stream; + +use crate::es::event; + +use super::Strategy; + +/// [`Strategy`] for some custom conversion provided by [`Customize`]. +#[derive(Clone, Copy, Debug)] +pub struct Custom; + +/// Convert `Event` into [`Stream`] of [`Transformed`] [`Event`]s for [`Custom`] +/// [`Strategy`]. +/// +/// [`Event`]: event::Event +/// [`Transformed`]: Self::Transformed +pub trait Customize +where + Event: event::Versioned, + Ctx: ?Sized, +{ + /// Error of this [`Strategy`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; + + /// Converts incoming [`Event`] into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform<'me, 'ctx, 'out>( + &'me self, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; +} + +impl Strategy for Custom +where + Adapter: Customize, + Event: event::Versioned, +{ + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'out> = Adapter::TransformedStream<'out>; + + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + Adapter::transform(adapter, event, context) + } +} diff --git a/core/src/es/adapter/transformer/strategy/initialized.rs b/core/src/es/adapter/transformer/strategy/initialized.rs new file mode 100644 index 0000000..78468e7 --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/initialized.rs @@ -0,0 +1,47 @@ +//! [`Initialized`] [`Strategy`] definition. + +use std::marker::PhantomData; + +use futures::{stream, TryStreamExt as _}; + +use crate::es::event; + +use super::{AsIs, Strategy}; + +/// [`Strategy`] for wrapping [`Event`]s in [`Initial`]. +/// +/// [`Event`]: crate::es::Event +/// [`Initial`]: event::Initial +#[derive(Clone, Debug)] +pub struct Initialized(PhantomData); + +impl Strategy + for Initialized +where + Ctx: ?Sized, + Event: event::Versioned, + InnerStrategy: Strategy, + InnerStrategy::Transformed: 'static, + InnerStrategy::Error: 'static, +{ + type Error = InnerStrategy::Error; + type Transformed = event::Initial; + type TransformedStream<'out> = stream::MapOk< + InnerStrategy::TransformedStream<'out>, + WrapInitial, + >; + + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) + } +} + +type WrapInitial = fn(Event) -> event::Initial; diff --git a/core/src/es/adapter/transformer/strategy/into.rs b/core/src/es/adapter/transformer/strategy/into.rs new file mode 100644 index 0000000..4aeb763 --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/into.rs @@ -0,0 +1,47 @@ +//! [`Into`] [`Strategy`] definition. + +use std::marker::PhantomData; + +use futures::{stream, TryStreamExt as _}; + +use crate::es::event; + +use super::{AsIs, Strategy}; + +/// [`Strategy`] for converting [`Event`]s using [`From`] impl. +/// +/// [`Event`]: crate::es::Event +#[derive(Copy, Clone, Debug)] +pub struct Into(PhantomData<(I, InnerStrategy)>); + +impl + Strategy for Into +where + Ctx: ?Sized, + Event: event::Versioned, + InnerStrategy: Strategy, + InnerStrategy::Transformed: 'static, + InnerStrategy::Error: 'static, + IntoEvent: From + 'static, +{ + type Error = InnerStrategy::Error; + type Transformed = IntoEvent; + type TransformedStream<'out> = stream::MapOk< + InnerStrategy::TransformedStream<'out>, + IntoFn, + >; + + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + ctx: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) + } +} + +type IntoFn = fn(FromEvent) -> IntoEvent; diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs new file mode 100644 index 0000000..3a83b7b --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -0,0 +1,100 @@ +//! [`Strategy`] definition and default implementations. + +pub mod as_is; +pub mod custom; +pub mod initialized; +pub mod into; +pub mod skip; +pub mod split; + +use futures::Stream; + +use crate::es::{adapter, event}; + +use super::{Transformer, WithStrategy}; + +#[doc(inline)] +pub use self::{ + as_is::AsIs, + custom::{Custom, Customize}, + initialized::Initialized, + into::Into, + skip::Skip, + split::{Split, Splitter}, +}; + +/// Generalized [`Transformer`] for [`Versioned`] events. +/// +/// [`Versioned`]: event::Versioned +pub trait Strategy +where + Event: event::Versioned, + Ctx: ?Sized, +{ + /// Error of this [`Strategy`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; + + /// Converts incoming [`Event`] into [`Transformed`]. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out; +} + +impl Transformer for adapter::Wrapper +where + Ctx: ?Sized, + Event: event::Versioned, + Adapter: WithStrategy + adapter::Returning, + Adapter::Strategy: Strategy, + ::Transformed: + From<>::Transformed>, + ::Error: + From<>::Error>, +{ + type Error = >::Error; + type Transformed = + >::Transformed; + type TransformedStream<'out> = >::TransformedStream<'out>; + + fn transform<'me, 'ctx, 'out>( + &'me self, + event: Event, + context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + >::transform( + &self.0, event, context, + ) + } +} diff --git a/core/src/es/adapter/transformer/strategy/skip.rs b/core/src/es/adapter/transformer/strategy/skip.rs new file mode 100644 index 0000000..7c5e324 --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/skip.rs @@ -0,0 +1,39 @@ +//! [`Skip`] [`Strategy`] definition. + +use futures::stream; + +use crate::es::{adapter, event}; + +use super::Strategy; + +/// [`Strategy`] for skipping [`Event`]s. +/// +/// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] +pub struct Skip; + +impl Strategy for Skip +where + Ctx: ?Sized, + Event: event::Versioned, + Adapter: adapter::Returning, + Adapter::Transformed: 'static, + Adapter::Error: 'static, +{ + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'out> = + stream::Empty>; + + fn transform<'me, 'ctx, 'out>( + _: &'me Adapter, + _: Event, + _: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + stream::empty() + } +} diff --git a/core/src/es/adapter/transformer/strategy/split.rs b/core/src/es/adapter/transformer/strategy/split.rs new file mode 100644 index 0000000..28c30da --- /dev/null +++ b/core/src/es/adapter/transformer/strategy/split.rs @@ -0,0 +1,68 @@ +//! [`Split`] [`Strategy`] definition. + +use std::marker::PhantomData; + +use futures::{stream, StreamExt as _}; + +use crate::es::{adapter, event}; + +use super::Strategy; + +/// [`Strategy`] for splitting single [`Event`] into multiple. Implement +/// [`Splitter`] to define splitting logic. +/// +/// [`Event`]: crate::es::Event +#[derive(Clone, Copy, Debug)] +pub struct Split(PhantomData); + +/// Split single [`Event`] into multiple for [`Split`] [`Strategy`]. +/// +/// [`Event`]: crate::es::Event +pub trait Splitter { + /// [`Iterator`] of split [`Event`]s. + /// + /// [`Event`]: crate::es::Event + type Iterator: Iterator; + + /// Splits [`Event`]. + /// + /// [`Event`]: crate::es::Event + fn split(&self, event: From) -> Self::Iterator; +} + +impl Strategy + for Split +where + Ctx: ?Sized, + Event: event::Versioned, + IntoEvent: 'static, + Adapter: Splitter + adapter::Returning, + Adapter::Iterator: 'static, + Adapter::Error: 'static, +{ + type Error = Adapter::Error; + type Transformed = ::Item; + type TransformedStream<'out> = SplitStream; + + fn transform<'me, 'ctx, 'out>( + adapter: &'me Adapter, + event: Event, + _: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + stream::iter(adapter.split(event)).map(Ok) + } +} + +type SplitStream = stream::Map< + stream::Iter<>::Iterator>, + fn( + <>::Iterator as Iterator>::Item, + ) -> Result< + <>::Iterator as Iterator>::Item, + ::Error, + >, +>; diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 9e0ac3e..9363ac9 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -212,12 +212,6 @@ impl> Sourced> } } -/// TODO -pub trait Upcast: Sized { - /// TODO - type Into: From; -} - #[cfg(feature = "codegen")] pub mod codegen { //! [`Event`] machinery aiding codegen. diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 668d638..cf1eb13 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -7,17 +7,17 @@ mod storage; use std::array; use arcana::es::{event::Sourced, EventAdapter as _}; -use futures::{stream, Stream, TryStreamExt as _}; +use futures::{stream, Stream, StreamExt as _, TryStreamExt as _}; #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::main] async fn main() { - let mut chat = Option::::None; - let mut message = Option::::None; - let mut email = Option::::None; + let mut events = stream::repeat_with(incoming_events).flatten(); + let events = events.by_ref(); + let mut chat = Option::::None; let chat_events = storage::chat::Adapter - .transform_all(incoming_events(), &()) + .transform_all(events.take(5), &()) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -33,8 +33,9 @@ async fn main() { }), ); + let mut email = Option::::None; let email_events = storage::email::Adapter - .transform_all(incoming_events(), &()) + .transform_all(events.take(5), &()) .inspect_ok(|ev| email.apply(ev)) .try_collect::>() .await @@ -50,8 +51,9 @@ async fn main() { }) ); + let mut message = Option::::None; let message_events = storage::message::Adapter - .transform_all(incoming_events(), &1) + .transform_all(events.take(5), &1) .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 208d11c..f480213 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,13 +1,10 @@ use std::convert::Infallible; -use arcana::es::adapter::{ - self, - transformer::{self, strategy}, -}; +use arcana::es::adapter::{self, strategy, WithStrategy}; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Chat; } @@ -15,38 +12,32 @@ impl adapter::WithError for Adapter { #[derive(Clone, Copy, Debug)] pub struct Adapter; -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Initialized>; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::AsIs; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index ea7a3d9..c8b3834 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,17 +1,14 @@ use std::{array, convert::Infallible}; use arcana::es::{ - adapter::{ - self, - transformer::{self, strategy}, - }, + adapter::{self, strategy, WithStrategy}, event::Initial, }; use either::Either; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Email; } @@ -19,38 +16,32 @@ impl adapter::WithError for Adapter { #[derive(Clone, Copy, Debug)] pub struct Adapter; -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Split>; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 4034911..0929f1d 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,14 +1,11 @@ use std::convert::Infallible; -use arcana::es::adapter::{ - self, - transformer::{self, strategy}, -}; +use arcana::es::adapter::{self, strategy, WithStrategy}; use futures::stream; use crate::event; -impl adapter::WithError for Adapter { +impl adapter::Returning for Adapter { type Error = Infallible; type Transformed = event::Message; } @@ -16,43 +13,36 @@ impl adapter::WithError for Adapter { #[derive(Debug)] pub struct Adapter; -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Custom; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy - for Adapter -{ +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl transformer::WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } // Basically same as Skip, but with additional Ctx bounds -impl strategy::CustomTransformer - for Adapter +impl strategy::Customize for Adapter where Ctx: From, { diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index c9616f2..ea0f08f 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,9 +7,6 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{ - Adapter, TransformedStream, WithError, Wrapper, + strategy, Adapter, Returning, Strategy, TransformedStream, WithStrategy, + Wrapper, }; - -#[cfg(feature = "derive")] -#[doc(inline)] -pub use arcana_core::es::adapter::codegen; diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index 1aea266..83da2db 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,6 +2,5 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Custom, CustomTransformer, Initialized, Into, Skip, Split, Splitter, - Strategy, + AsIs, Custom, Customize, Initialized, Into, Skip, Split, Splitter, Strategy, }; From 09c0207084496bda40f5f40b0d19d919def3fe17 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Sep 2021 11:47:27 +0300 Subject: [PATCH 074/104] Add event::Raw and usage examples [skip ci] --- codegen/impl/Cargo.toml | 1 - core/src/es/adapter/mod.rs | 25 ++-- core/src/es/adapter/transformer/mod.rs | 6 +- .../es/adapter/transformer/strategy/as_is.rs | 2 +- .../es/adapter/transformer/strategy/custom.rs | 4 +- .../transformer/strategy/initialized.rs | 2 +- .../es/adapter/transformer/strategy/into.rs | 2 +- .../es/adapter/transformer/strategy/mod.rs | 7 +- .../es/adapter/transformer/strategy/skip.rs | 2 +- .../es/adapter/transformer/strategy/split.rs | 6 +- core/src/es/event.rs | 124 ++++++++++++++--- examples/chat/Cargo.toml | 2 + examples/chat/src/event/chat.rs | 6 +- examples/chat/src/event/email.rs | 14 +- examples/chat/src/event/message.rs | 2 +- examples/chat/src/event/mod.rs | 8 +- examples/chat/src/main.rs | 77 +---------- examples/chat/src/storage/chat.rs | 10 +- examples/chat/src/storage/email.rs | 79 ++++++++++- examples/chat/src/storage/message.rs | 10 +- examples/chat/src/storage/mod.rs | 125 +++++++++++++++++- src/es/event.rs | 3 +- src/es/mod.rs | 5 +- 23 files changed, 369 insertions(+), 153 deletions(-) diff --git a/codegen/impl/Cargo.toml b/codegen/impl/Cargo.toml index de5a3e5..574fcc0 100644 --- a/codegen/impl/Cargo.toml +++ b/codegen/impl/Cargo.toml @@ -17,7 +17,6 @@ readme = "README.md" doc = ["arcana-core", "futures"] # only for generating documentation [dependencies] -itertools = "0.10" proc-macro2 = { version = "1.0.4", default-features = false } quote = { version = "1.0.9", default-features = false } syn = { version = "1.0.72", features = ["derive", "extra-traits", "parsing", "printing"], default-features = false } diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 5f45e31..d3efdb0 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -153,7 +153,9 @@ pub trait Adapter { >, > + 'out where - Events: 'out; + Ctx: 'out, + Events: 'out, + Self: 'out; /// Converts all incoming [`Event`]s into [`Transformed`]. /// @@ -171,20 +173,21 @@ pub trait Adapter { impl Adapter for A where - Events: Stream, - Ctx: 'static + ?Sized, A: Returning, - Wrapper: Transformer + 'static, - ::Transformed: + Ctx: ?Sized, + Events: Stream, + Wrapper: Transformer, + A::Transformed: From< as Transformer>::Transformed>, - ::Error: - From< as Transformer>::Error>, + A::Error: From< as Transformer>::Error>, { type Error = ::Error; type Transformed = ::Transformed; type TransformedStream<'out> where + Ctx: 'out, Events: 'out, + Self: 'out, = TransformedStream<'out, Wrapper, Events, Ctx>; fn transform_all<'me, 'ctx, 'out>( @@ -220,9 +223,9 @@ where /// [`Stream`] for [`Adapter`] blanket impl. pub struct TransformedStream<'out, Adapter, Events, Ctx> where - Events: Stream, Adapter: Transformer, Ctx: ?Sized, + Events: Stream, { #[pin] events: Events, @@ -236,9 +239,9 @@ where impl<'out, Adapter, Events, Ctx> Debug for TransformedStream<'out, Adapter, Events, Ctx> where - Events: Debug + Stream, Adapter: Debug + Transformer, Ctx: Debug + ?Sized, + Events: Debug + Stream, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("TransformStream") @@ -261,9 +264,9 @@ type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> where - Events: Stream, Adapter: Transformer, Ctx: ?Sized, + Events: Stream, { fn new(adapter: &'out Adapter, events: Events, context: &'out Ctx) -> Self { Self { @@ -278,9 +281,9 @@ where impl<'out, Adapter, Events, Ctx> Stream for TransformedStream<'out, Adapter, Events, Ctx> where - Events: Stream, Ctx: ?Sized, Adapter: Transformer + Returning, + Events: Stream, ::Transformed: From<>::Transformed>, ::Error: diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 313d97d..4e0fc91 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -15,10 +15,7 @@ pub use strategy::Strategy; /// [`Event`]: crate::es::Event /// [`Returning`]: super::Returning /// [`VersionedEvent`]: crate::es::VersionedEvent -pub trait WithStrategy -where - Self: Sized, -{ +pub trait WithStrategy { /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event @@ -37,6 +34,7 @@ where /// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. /// See [`Adapter`] for more info. /// +/// [`Adapter`]: crate::es::Adapter /// [`Custom`]: strategy::Custom /// [`Event`]: crate::es::Event /// [`Skip`]: strategy::Skip diff --git a/core/src/es/adapter/transformer/strategy/as_is.rs b/core/src/es/adapter/transformer/strategy/as_is.rs index 8a8be5e..2aca067 100644 --- a/core/src/es/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/adapter/transformer/strategy/as_is.rs @@ -17,7 +17,7 @@ where Adapter: adapter::Returning, Adapter::Error: 'static, Ctx: ?Sized, - Event: event::Versioned + 'static, + Event: event::VersionedOrRaw + 'static, { type Error = Adapter::Error; type Transformed = Event; diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index ffc3d57..0eb03a1 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -17,7 +17,7 @@ pub struct Custom; /// [`Transformed`]: Self::Transformed pub trait Customize where - Event: event::Versioned, + Event: event::VersionedOrRaw, Ctx: ?Sized, { /// Error of this [`Strategy`]. @@ -56,7 +56,7 @@ where impl Strategy for Custom where Adapter: Customize, - Event: event::Versioned, + Event: event::VersionedOrRaw, { type Error = Adapter::Error; type Transformed = Adapter::Transformed; diff --git a/core/src/es/adapter/transformer/strategy/initialized.rs b/core/src/es/adapter/transformer/strategy/initialized.rs index 78468e7..4b7b4da 100644 --- a/core/src/es/adapter/transformer/strategy/initialized.rs +++ b/core/src/es/adapter/transformer/strategy/initialized.rs @@ -19,7 +19,7 @@ impl Strategy for Initialized where Ctx: ?Sized, - Event: event::Versioned, + Event: event::VersionedOrRaw, InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, diff --git a/core/src/es/adapter/transformer/strategy/into.rs b/core/src/es/adapter/transformer/strategy/into.rs index 4aeb763..c0014f7 100644 --- a/core/src/es/adapter/transformer/strategy/into.rs +++ b/core/src/es/adapter/transformer/strategy/into.rs @@ -18,7 +18,7 @@ impl Strategy for Into where Ctx: ?Sized, - Event: event::Versioned, + Event: event::VersionedOrRaw, InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index 3a83b7b..4ee1d71 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -28,7 +28,6 @@ pub use self::{ /// [`Versioned`]: event::Versioned pub trait Strategy where - Event: event::Versioned, Ctx: ?Sized, { /// Error of this [`Strategy`]. @@ -67,12 +66,12 @@ where impl Transformer for adapter::Wrapper where Ctx: ?Sized, - Event: event::Versioned, + Event: event::VersionedOrRaw, Adapter: WithStrategy + adapter::Returning, Adapter::Strategy: Strategy, - ::Transformed: + Adapter::Transformed: From<>::Transformed>, - ::Error: + Adapter::Error: From<>::Error>, { type Error = >::Error; diff --git a/core/src/es/adapter/transformer/strategy/skip.rs b/core/src/es/adapter/transformer/strategy/skip.rs index 7c5e324..e96bda2 100644 --- a/core/src/es/adapter/transformer/strategy/skip.rs +++ b/core/src/es/adapter/transformer/strategy/skip.rs @@ -15,7 +15,7 @@ pub struct Skip; impl Strategy for Skip where Ctx: ?Sized, - Event: event::Versioned, + Event: event::VersionedOrRaw, Adapter: adapter::Returning, Adapter::Transformed: 'static, Adapter::Error: 'static, diff --git a/core/src/es/adapter/transformer/strategy/split.rs b/core/src/es/adapter/transformer/strategy/split.rs index 28c30da..d8fae85 100644 --- a/core/src/es/adapter/transformer/strategy/split.rs +++ b/core/src/es/adapter/transformer/strategy/split.rs @@ -33,12 +33,12 @@ pub trait Splitter { impl Strategy for Split where - Ctx: ?Sized, - Event: event::Versioned, - IntoEvent: 'static, Adapter: Splitter + adapter::Returning, Adapter::Iterator: 'static, Adapter::Error: 'static, + Ctx: ?Sized, + Event: event::VersionedOrRaw, + IntoEvent: 'static, { type Error = Adapter::Error; type Transformed = ::Item; diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 9363ac9..cd2aebb 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -1,6 +1,6 @@ //! [`Event`] machinery. -use std::{convert::TryFrom, num::NonZeroU16}; +use std::{convert::TryFrom, marker::PhantomData, num::NonZeroU16}; use derive_more::{Deref, DerefMut, Display, Into}; use ref_cast::RefCast; @@ -192,7 +192,19 @@ pub trait Initialized { /// Wrapper type to mark an [`Event`] that makes some [`Sourced`] state being /// [`Initialized`]. -#[derive(Clone, Copy, Debug, Deref, DerefMut, Display, RefCast)] +#[derive( + Clone, + Copy, + Debug, + Deref, + DerefMut, + Display, + Eq, + Ord, + PartialEq, + PartialOrd, + RefCast, +)] #[repr(transparent)] pub struct Initial(pub Ev); @@ -212,34 +224,91 @@ impl> Sourced> } } +// TODO: Maybe replace `Ev` with `const Name` (same as `const &'static str`), +// once `adt_const_params` feature stabilizes. +// https://github.com/rust-lang/rust/issues/44580 +/// Raw [`Versioned`] event. +/// +/// This type is useful for repository-level [`Event`]s to avoid necessity of +/// describing every [`Version`] as a separate type. +/// +/// As [`Event::version()`] is infallible method, every instance of this type +/// has to store [`Version`]. Deserialization errors should be handled before +/// creation or as a part of an [`Adapter`] implementation. +/// +/// > __WARNING:__ This type isn't considered in const assertion generated by +/// > [`Event`] derive macro, ensuring that every combination of +/// > [`Name`] and [`Version`] are corresponding to a single Rust +/// > type. That means if your [`Event`] enum has some concrete +/// > types with same [`Name`] as `Ev`, this struct should be +/// > considered as a fallback for [`Version`]s not covered with +/// > those concrete types. +/// +/// [`Adapter`]: crate::es::Adapter +#[derive(Clone, Copy, Debug, Deref, DerefMut)] +pub struct Raw { + /// Inner [`Event`] data. + #[deref] + #[deref_mut] + pub data: Data, + + /// This [`Event`] [`Version`]. + pub version: Version, + + /// Used for [`Versioned::NAME`] in [`Event`] implementation. + _event: PhantomData, +} + +impl Raw { + /// Creates new [`Raw`]. + #[must_use] + pub fn new(data: Data, version: Version) -> Self { + Self { + data, + version, + _event: PhantomData, + } + } +} + +impl Event for Raw +where + Ev: Versioned + ?Sized, +{ + fn name(&self) -> Name { + Ev::NAME + } + + fn version(&self) -> Version { + self.version + } +} + +/// Marker trait for [`Versioned`] or [`Raw`] [`Event`]s. Used for [`Adapter`] +/// specialization. +/// +/// [`Adapter`]: crate::es::Adapter +pub trait VersionedOrRaw {} + +impl VersionedOrRaw for Raw +where + Ev: ?Sized, + Raw: Event, +{ +} + +impl VersionedOrRaw for Ev {} + #[cfg(feature = "codegen")] pub mod codegen { //! [`Event`] machinery aiding codegen. use sealed::sealed; - use super::{Event, Initial}; + use super::{Event, Initial, Raw}; pub use futures; - /// TODO - pub trait EnumSize { - /// TODO - const SIZE: usize; - } - - /// TODO - pub trait Get { - /// TODO - type Out; - - /// TODO - fn get(&self) -> Option<&Self::Out>; - - /// TODO - fn unwrap(self) -> Self::Out; - } - /// Custom [`Borrow`] codegen aiding trait for borrowing an [`Event`] either /// from itself or from an [`Initial`] wrapper. /// @@ -289,6 +358,15 @@ pub mod codegen { type Type = Ev; } + impl Raw { + #[doc(hidden)] + #[must_use] + pub const fn __arcana_events() -> [(&'static str, &'static str, u16); 0] + { + [] + } + } + /// Tracking of [`VersionedEvent`]s number. /// /// [`VersionedEvent`]: super::Versioned @@ -303,6 +381,10 @@ pub mod codegen { const COUNT: usize = Ev::COUNT; } + impl Versioned for Raw { + const COUNT: usize = 0; + } + /// Checks in compile time whether all the given combinations of /// [`Event::name`] and [`Event::version`] correspond to different Rust /// types. diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index dac141e..3522b39 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -15,4 +15,6 @@ arcana = { version = "0.1.0-dev", path = "../..", features = ["derive", "es"] } derive_more = "0.99" either = "1" futures = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tokio = { version = "1", features = ["full"] } diff --git a/examples/chat/src/event/chat.rs b/examples/chat/src/event/chat.rs index cc6cc94..5027b4b 100644 --- a/examples/chat/src/event/chat.rs +++ b/examples/chat/src/event/chat.rs @@ -3,7 +3,7 @@ use arcana::es::event; pub mod private { use super::event; - #[derive(Debug, event::Versioned)] + #[derive(Debug, PartialEq, event::Versioned)] #[event(name = "chat.private.created", version = 2)] pub struct Created; } @@ -11,7 +11,7 @@ pub mod private { pub mod public { use super::event; - #[derive(Debug, event::Versioned)] + #[derive(Debug, PartialEq, event::Versioned)] #[event(name = "chat.public.created", version = 2)] pub struct Created; } @@ -19,7 +19,7 @@ pub mod public { pub mod v1 { use super::event; - #[derive(Debug, event::Versioned)] + #[derive(Debug, PartialEq, event::Versioned)] #[event(name = "chat.created", version = 1)] pub struct Created; } diff --git a/examples/chat/src/event/email.rs b/examples/chat/src/event/email.rs index 05cd16e..ae2dc6c 100644 --- a/examples/chat/src/event/email.rs +++ b/examples/chat/src/event/email.rs @@ -1,22 +1,22 @@ use arcana::es::event; -#[derive(Debug, event::Versioned)] -#[event(name = "email.added", version = 2)] +#[derive(Debug, PartialEq, event::Versioned)] +#[event(name = "email.added", version = 3)] pub struct Added { pub email: String, } -#[derive(Debug, event::Versioned)] -#[event(name = "email.confirmed", version = 2)] +#[derive(Debug, PartialEq, event::Versioned)] +#[event(name = "email.confirmed", version = 3)] pub struct Confirmed { pub confirmed_by: String, } -pub mod v1 { +pub mod v2 { use super::event; - #[derive(Debug, event::Versioned)] - #[event(name = "email.added_and_confirmed", version = 1)] + #[derive(Debug, serde::Deserialize, PartialEq, event::Versioned)] + #[event(name = "email.added_and_confirmed", version = 2)] pub struct AddedAndConfirmed { pub email: String, pub confirmed_by: Option, diff --git a/examples/chat/src/event/message.rs b/examples/chat/src/event/message.rs index fa34a7d..66f69f6 100644 --- a/examples/chat/src/event/message.rs +++ b/examples/chat/src/event/message.rs @@ -1,5 +1,5 @@ use arcana::es::event; -#[derive(Debug, event::Versioned)] +#[derive(Debug, PartialEq, event::Versioned)] #[event(name = "message.posted", version = 1)] pub struct Posted; diff --git a/examples/chat/src/event/mod.rs b/examples/chat/src/event/mod.rs index a41d628..9c1245b 100644 --- a/examples/chat/src/event/mod.rs +++ b/examples/chat/src/event/mod.rs @@ -5,20 +5,22 @@ pub mod message; use arcana::es::{event, Event}; use derive_more::From; -#[derive(Debug, Event, From)] +pub use event::{Initial, Raw, Version}; + +#[derive(Debug, Event, From, PartialEq)] pub enum Chat { PrivateCreated(event::Initial), PublicCreated(event::Initial), MessagePosted(message::Posted), } -#[derive(Debug, Event, From)] +#[derive(Debug, Event, From, PartialEq)] pub enum Email { Added(event::Initial), Confirmed(email::Confirmed), } -#[derive(Debug, Event, From)] +#[derive(Debug, Event, From, PartialEq)] pub enum Message { MessagePosted(event::Initial), } diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index cf1eb13..7febfbb 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -4,79 +4,4 @@ mod domain; mod event; mod storage; -use std::array; - -use arcana::es::{event::Sourced, EventAdapter as _}; -use futures::{stream, Stream, StreamExt as _, TryStreamExt as _}; - -#[allow(clippy::semicolon_if_nothing_returned)] -#[tokio::main] -async fn main() { - let mut events = stream::repeat_with(incoming_events).flatten(); - let events = events.by_ref(); - - let mut chat = Option::::None; - let chat_events = storage::chat::Adapter - .transform_all(events.take(5), &()) - .inspect_ok(|ev| chat.apply(ev)) - .try_collect::>() - .await - .unwrap(); - println!("{:?}", chat_events); - - assert_eq!(chat_events.len(), 4); - assert_eq!( - chat, - Some(domain::Chat { - visibility: domain::chat::Visibility::Public, - message_count: 1 - }), - ); - - let mut email = Option::::None; - let email_events = storage::email::Adapter - .transform_all(events.take(5), &()) - .inspect_ok(|ev| email.apply(ev)) - .try_collect::>() - .await - .unwrap(); - println!("{:?}", email_events); - - assert_eq!(email_events.len(), 2); - assert_eq!( - email, - Some(domain::Email { - value: "hello@world.com".to_owned(), - confirmed_by: Some("Test".to_owned()), - }) - ); - - let mut message = Option::::None; - let message_events = storage::message::Adapter - .transform_all(events.take(5), &1) - .inspect_ok(|ev| message.apply(ev)) - .try_collect::>() - .await - .unwrap(); - println!("{:?}", message_events); - - assert_eq!(message_events.len(), 1); - assert_eq!(message, Some(domain::Message)); -} - -fn incoming_events() -> impl Stream { - stream::iter(array::IntoIter::new([ - storage::ChatEvent::Created(event::chat::v1::Created).into(), - storage::ChatEvent::PrivateCreated(event::chat::private::Created) - .into(), - storage::ChatEvent::PublicCreated(event::chat::public::Created).into(), - storage::MessageEvent::Posted(event::message::Posted).into(), - storage::EmailEvent::AddedAndConfirmed( - event::email::v1::AddedAndConfirmed { - email: "hello@world.com".to_owned(), - confirmed_by: Some("Test".to_owned()), - }, - ) - .into(), - ])) -} +fn main() {} diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index f480213..019e083 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -29,7 +29,7 @@ impl WithStrategy for Adapter { type Strategy = strategy::AsIs; } -impl WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } @@ -41,6 +41,14 @@ impl WithStrategy for Adapter { type Strategy = strategy::Skip; } +impl + WithStrategy< + event::Raw, + > for Adapter +{ + type Strategy = strategy::Skip; +} + // Chats are private by default. impl From for event::chat::private::Created { fn from(_: event::chat::v1::Created) -> Self { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index c8b3834..53bc0c5 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,15 +1,16 @@ -use std::{array, convert::Infallible}; +use std::{array, iter}; use arcana::es::{ - adapter::{self, strategy, WithStrategy}, + adapter::{self, strategy, strategy::Splitter, WithStrategy}, event::Initial, }; use either::Either; +use futures::{future, stream, StreamExt as _}; use crate::event; impl adapter::Returning for Adapter { - type Error = Infallible; + type Error = serde_json::Error; type Transformed = event::Email; } @@ -20,7 +21,7 @@ impl WithStrategy for Adapter { type Strategy = strategy::Initialized; } -impl WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Split>; } @@ -46,8 +47,16 @@ impl WithStrategy for Adapter { } impl - strategy::Splitter< - event::email::v1::AddedAndConfirmed, + WithStrategy< + event::Raw, + > for Adapter +{ + type Strategy = strategy::Custom; +} + +impl + Splitter< + event::email::v2::AddedAndConfirmed, Either, > for Adapter { @@ -55,7 +64,7 @@ impl fn split( &self, - event: event::email::v1::AddedAndConfirmed, + event: event::email::v2::AddedAndConfirmed, ) -> Self::Iterator { use either::{Left, Right}; @@ -78,6 +87,62 @@ type SplitEmail = Either< array::IntoIter, 2>, >; +impl + strategy::Customize< + event::Raw, + Ctx, + > for Adapter +{ + type Error = serde_json::Error; + type Transformed = Either; + type TransformedStream<'out> = CustomizedStream; + + fn transform<'me, 'ctx, 'out>( + &'me self, + event: event::Raw< + event::email::v2::AddedAndConfirmed, + serde_json::Value, + >, + _context: &'ctx Ctx, + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + { + match serde_json::from_value::( + event.data, + ) { + Ok(ev) => { + let ok: fn(_) -> _ = Ok; + stream::iter(Adapter.split(ev).map(ok)).left_stream() + } + Err(err) => stream::once(future::ready(Err(err))).right_stream(), + } + } +} + +type CustomizedStream = future::Either< + stream::Iter< + iter::Map< + SplitEmail, + fn( + Either, + ) -> Result< + Either, + serde_json::Error, + >, + >, + >, + stream::Once< + future::Ready< + Result< + Either, + serde_json::Error, + >, + >, + >, +>; + impl From> for event::Email { diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 0929f1d..2ab2549 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -29,7 +29,7 @@ impl WithStrategy for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl WithStrategy for Adapter { type Strategy = strategy::Skip; } @@ -41,6 +41,14 @@ impl WithStrategy for Adapter { type Strategy = strategy::Skip; } +impl + WithStrategy< + event::Raw, + > for Adapter +{ + type Strategy = strategy::Skip; +} + // Basically same as Skip, but with additional Ctx bounds impl strategy::Customize for Adapter where diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 6752beb..3be3359 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -30,5 +30,128 @@ pub enum MessageEvent { pub enum EmailEvent { Added(event::email::Added), Confirmed(event::email::Confirmed), - AddedAndConfirmed(event::email::v1::AddedAndConfirmed), + AddedAndConfirmed(event::email::v2::AddedAndConfirmed), + RawAddedAndConfirmed( + event::Raw, + ), +} + +#[cfg(test)] +mod spec { + use std::array; + + use arcana::es::{EventAdapter as _, EventSourced as _}; + use futures::{stream, Stream, TryStreamExt as _}; + use serde_json::json; + + use crate::domain; + + use super::{ + chat, email, event, message, ChatEvent, EmailEvent, Event, MessageEvent, + }; + + #[allow(clippy::semicolon_if_nothing_returned)] + #[tokio::test] + async fn chat_adapter() { + let mut chat = Option::::None; + let chat_events = chat::Adapter + .transform_all(incoming_events(), &()) + .inspect_ok(|ev| chat.apply(ev)) + .try_collect::>() + .await + .unwrap(); + + assert_eq!( + chat_events, + vec![ + event::Initial(event::chat::private::Created).into(), + event::Initial(event::chat::private::Created).into(), + event::Initial(event::chat::public::Created).into(), + event::message::Posted.into() + ] + ); + assert_eq!( + chat, + Some(domain::Chat { + visibility: domain::chat::Visibility::Public, + message_count: 1 + }), + ); + } + + #[allow(clippy::semicolon_if_nothing_returned)] + #[tokio::test] + async fn email_adapter() { + let mut email = Option::::None; + let email_events = email::Adapter + .transform_all(incoming_events(), &()) + .inspect_ok(|ev| email.apply(ev)) + .try_collect::>() + .await + .unwrap(); + + assert_eq!( + email_events, + vec![ + event::Initial(event::email::Added { + email: "hello@world.com".to_string() + }) + .into(), + event::Initial(event::email::Added { + email: "raw@event.com".to_string() + }) + .into(), + event::email::Confirmed { + confirmed_by: "User".to_string() + } + .into(), + ] + ); + assert_eq!( + email, + Some(domain::Email { + value: "raw@event.com".to_owned(), + confirmed_by: Some("User".to_owned()), + }) + ); + } + + #[allow(clippy::semicolon_if_nothing_returned)] + #[tokio::test] + async fn message_adapter() { + let mut message = Option::::None; + let message_events = message::Adapter + .transform_all(incoming_events(), &1) + .inspect_ok(|ev| message.apply(ev)) + .try_collect::>() + .await + .unwrap(); + + assert_eq!( + message_events, + vec![event::Initial(event::message::Posted).into()], + ); + assert_eq!(message, Some(domain::Message)); + } + + fn incoming_events() -> impl Stream { + stream::iter(array::IntoIter::new([ + ChatEvent::Created(event::chat::v1::Created).into(), + ChatEvent::PrivateCreated(event::chat::private::Created).into(), + ChatEvent::PublicCreated(event::chat::public::Created).into(), + MessageEvent::Posted(event::message::Posted).into(), + EmailEvent::AddedAndConfirmed( + event::email::v2::AddedAndConfirmed { + email: "hello@world.com".to_owned(), + confirmed_by: None, + }, + ) + .into(), + EmailEvent::RawAddedAndConfirmed(event::Raw::new( + json!({ "email": "raw@event.com", "confirmed_by": "User" }), + event::Version::try_new(1).unwrap(), + )) + .into(), + ])) + } } diff --git a/src/es/event.rs b/src/es/event.rs index bac97e7..ea931b5 100644 --- a/src/es/event.rs +++ b/src/es/event.rs @@ -2,7 +2,8 @@ #[doc(inline)] pub use arcana_core::es::event::{ - Event, Initial, Initialized, Name, Sourced, Sourcing, Version, Versioned, + Event, Initial, Initialized, Name, Raw, Sourced, Sourcing, Version, + Versioned, VersionedOrRaw, }; #[cfg(feature = "derive")] diff --git a/src/es/mod.rs b/src/es/mod.rs index 84af534..93f20d0 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -13,6 +13,7 @@ pub use self::adapter::{ #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, - Name as EventName, Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, + Name as EventName, Raw as RawEvent, Sourced as EventSourced, + Sourcing as EventSourcing, Version as EventVersion, + Versioned as VersionedEvent, }; From 2cca7ac764578aa556a37b61143f6e1280d025a4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Sep 2021 12:50:33 +0300 Subject: [PATCH 075/104] Corrections --- Cargo.toml | 8 -- codegen/Cargo.toml | 8 +- codegen/impl/src/es/event/mod.rs | 79 +++++++++++-------- core/Cargo.toml | 10 +-- core/src/es/adapter/mod.rs | 4 +- .../es/adapter/transformer/strategy/custom.rs | 3 +- core/src/es/event.rs | 11 ++- core/src/es/mod.rs | 5 +- examples/chat/src/storage/mod.rs | 2 +- src/es/mod.rs | 4 +- 10 files changed, 69 insertions(+), 65 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 545d723..fd1d6c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,6 @@ es = ["arcana-core/es"] arcana-core = { version = "0.1.0-dev", path = "./core" } arcana-codegen = { version = "0.1.0-dev", path = "./codegen", optional = true } -[dev-dependencies] -derive_more = "0.99" -either = "1" -futures = "0.3" -tokio = { version = "1", features = ["full"] } -pin-project = "1" -static_assertions = "1" - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 45fe05e..71a3370 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -14,14 +14,16 @@ repository = "https://github.com/arcana-rs/arcana" readme = "README.md" [features] -doc = ["arcana-codegen-shim/doc"] # only for generating documentation +doc = ["arcana-codegen-shim/doc", "arcana-core", "futures"] # only for generating documentation [dependencies] -arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"] } arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } -futures = { version = "0.3", default-features = false } static_assertions = { version = "1.1", default-features = false } +# `doc` feature +arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } +futures = { version = "0.3", default-features = false, optional = true } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index df058a6..45be548 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -198,7 +198,7 @@ impl Definition { } /// Generates code to derive [`event::Sourced`][0] trait, by simply matching - /// each enum variant, which is expected to have itself + /// each enum variant, which is expected to have itself an /// [`event::Sourced`][0] implementation. /// /// [0]: arcana_core::es::event::Sourced @@ -208,6 +208,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); let turbofish_gens = ty_gens.as_turbofish(); + let var = self.variants.iter().map(|v| &v.ident); let var_ty = self.variant_types(); let mut ext_gens = self.generics.clone(); @@ -217,8 +218,6 @@ impl Definition { }); let (impl_gens, _, where_clause) = ext_gens.split_for_impl(); - let var = self.variants.iter().map(|v| &v.ident); - let unreachable_arm = self.has_ignored_variants.then(|| { quote! { _ => unreachable!(), } }); @@ -230,9 +229,11 @@ impl Definition { { fn apply(&mut self, event: &#ty#ty_gens) { match event { - #(#ty#turbofish_gens::#var(f) => { - ::arcana::es::event::Sourced::apply(self, f) - },)* + #( + #ty#turbofish_gens::#var(f) => { + ::arcana::es::event::Sourced::apply(self, f); + }, + )* #unreachable_arm } } @@ -390,19 +391,27 @@ impl Definition { syn::Token![,], > = parse_quote! { __A: ::arcana::es::adapter::Returning, - Self: #( ::arcana::es::adapter::Transformer<#var_type, __Ctx> )+*, + Self: #( + ::arcana::es::adapter::Transformer<#var_type, __Ctx> + )+*, <__A as ::arcana::es::adapter::Returning>::Transformed: - #( ::std::convert::From<>::Transformed> +)* - 'static, + 'static + #( + + ::std::convert::From<>::Transformed> + )*, <__A as ::arcana::es::adapter::Returning>::Error: - #( ::std::convert::From<>::Error> +)* - 'static, - #( >:: + 'static + #( + + ::std::convert::From<>::Error> + )*, + #( + >:: Transformed: 'static, - >:: - Error: 'static, )* + >:: + Error: 'static, + )* }; generics.params.extend(additional_generic_params); @@ -476,7 +485,7 @@ impl Definition { } /// Generates code for implementation of a [`Transformer::transform()`][0] - /// fn. + /// method. /// /// Generated code matches over every [`Event`]'s variant and makes it /// compatible with [`Definition::transformed_stream()`] type with @@ -598,10 +607,10 @@ mod spec { fn apply(&mut self, event: &Event) { match event { Event::File(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, Event::Chat(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, } } @@ -616,17 +625,17 @@ mod spec { ::arcana::es::adapter::Transformer + ::arcana::es::adapter::Transformer, <__A as ::arcana::es::adapter::Returning>::Transformed: + 'static + ::std::convert::From< >::Transformed> + ::std::convert::From< >::Transformed> + - 'static, + Transformer >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: + 'static + ::std::convert::From< >::Error> + ::std::convert::From< >::Error> + - 'static, + Transformer >::Error>, >::Transformed: 'static, ) { match event { Event::<'a, F, C>::File(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, Event::<'a, F, C>::Chat(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, } } @@ -884,17 +893,17 @@ mod spec { ::arcana::es::adapter::Transformer, __Ctx> + ::arcana::es::adapter::Transformer, __Ctx>, <__A as ::arcana::es::adapter::Returning>::Transformed: + 'static + ::std::convert::From< , __Ctx> >::Transformed> + ::std::convert::From< , __Ctx> >::Transformed> + - 'static, + Transformer, __Ctx> >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: + 'static + ::std::convert::From< , __Ctx> >::Error> + ::std::convert::From< , __Ctx> >::Error> + - 'static, + Transformer, __Ctx> >::Error>, , __Ctx> >::Transformed: 'static, @@ -1157,10 +1166,10 @@ mod spec { fn apply(&mut self, event: &Event) { match event { Event::File(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, Event::Chat(f) => { - ::arcana::es::event::Sourced::apply(self, f) + ::arcana::es::event::Sourced::apply(self, f); }, _ => unreachable!(), } @@ -1176,17 +1185,17 @@ mod spec { ::arcana::es::adapter::Transformer + ::arcana::es::adapter::Transformer, <__A as ::arcana::es::adapter::Returning>::Transformed: + 'static + ::std::convert::From< >::Transformed> + ::std::convert::From< >::Transformed> + - 'static, + Transformer >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: + 'static + ::std::convert::From< >::Error> + ::std::convert::From< >::Error> + - 'static, + Transformer >::Error>, >::Transformed: 'static, where Adapter: Transformer, diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index 0eb03a1..ba85072 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -10,10 +10,9 @@ use super::Strategy; #[derive(Clone, Copy, Debug)] pub struct Custom; -/// Convert `Event` into [`Stream`] of [`Transformed`] [`Event`]s for [`Custom`] +/// Convert `Event` into [`Stream`] of [`Transformed`] for [`Custom`] /// [`Strategy`]. /// -/// [`Event`]: event::Event /// [`Transformed`]: Self::Transformed pub trait Customize where diff --git a/core/src/es/event.rs b/core/src/es/event.rs index cd2aebb..eefc0a6 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -200,6 +200,7 @@ pub trait Initialized { DerefMut, Display, Eq, + Hash, Ord, PartialEq, PartialOrd, @@ -237,15 +238,17 @@ impl> Sourced> /// creation or as a part of an [`Adapter`] implementation. /// /// > __WARNING:__ This type isn't considered in const assertion generated by -/// > [`Event`] derive macro, ensuring that every combination of -/// > [`Name`] and [`Version`] are corresponding to a single Rust +/// > [`Event`] derive macro, which ensures that every combination +/// > of [`Name`] and [`Version`] corresponds to a single Rust /// > type. That means if your [`Event`] enum has some concrete /// > types with same [`Name`] as `Ev`, this struct should be /// > considered as a fallback for [`Version`]s not covered with /// > those concrete types. /// /// [`Adapter`]: crate::es::Adapter -#[derive(Clone, Copy, Debug, Deref, DerefMut)] +#[derive( + Clone, Copy, Debug, Deref, DerefMut, Eq, Hash, Ord, PartialEq, PartialOrd, +)] pub struct Raw { /// Inner [`Event`] data. #[deref] @@ -260,7 +263,7 @@ pub struct Raw { } impl Raw { - /// Creates new [`Raw`]. + /// Creates a new [`Raw`]. #[must_use] pub fn new(data: Data, version: Version) -> Self { Self { diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 81189a0..3af84bf 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -11,6 +11,7 @@ pub use self::adapter::Adapter; #[doc(inline)] pub use self::event::{ Event, Initial as InitialEvent, Initialized as EventInitialized, - Name as EventName, Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, + Name as EventName, Raw as RawEvent, Sourced as EventSourced, + Sourcing as EventSourcing, Version as EventVersion, + Versioned as VersionedEvent, }; diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 3be3359..ad361b2 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -40,7 +40,7 @@ pub enum EmailEvent { mod spec { use std::array; - use arcana::es::{EventAdapter as _, EventSourced as _}; + use arcana::es::{Adapter as _, EventSourced as _}; use futures::{stream, Stream, TryStreamExt as _}; use serde_json::json; diff --git a/src/es/mod.rs b/src/es/mod.rs index 93f20d0..3af84bf 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -6,9 +6,7 @@ pub mod adapter; pub mod event; #[doc(inline)] -pub use self::adapter::{ - Adapter as EventAdapter, Transformer as EventTransformer, -}; +pub use self::adapter::Adapter; #[doc(inline)] pub use self::event::{ From 025d35b37fce12d8a3ea736c8c5facd9bca62b08 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Sep 2021 13:04:50 +0300 Subject: [PATCH 076/104] Fix deps --- core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index cebd942..7114f7c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,7 +18,7 @@ codegen = ["sealed"] # only enables codegen glue es = [] [dependencies] -derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "into"], default-features = false } +derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "from", "into"], default-features = false } futures = { version = "0.3", default-features = false } pin-project = "1.0" ref-cast = "1.0" From 2e06c30160ee11ba8e84b7315aae00542b7eb82b Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 29 Sep 2021 12:34:36 +0300 Subject: [PATCH 077/104] Some bikesheddings --- core/src/es/adapter/mod.rs | 36 +++++++------ core/src/es/adapter/transformer/mod.rs | 10 ++-- .../es/adapter/transformer/strategy/as_is.rs | 18 +++---- .../es/adapter/transformer/strategy/custom.rs | 36 +++++-------- .../transformer/strategy/initialized.rs | 47 ---------------- .../es/adapter/transformer/strategy/into.rs | 18 +++---- .../es/adapter/transformer/strategy/mod.rs | 54 +++++++++---------- .../es/adapter/transformer/strategy/skip.rs | 18 +++---- .../es/adapter/transformer/strategy/split.rs | 19 +++---- examples/chat/src/storage/chat.rs | 22 ++++---- examples/chat/src/storage/email.rs | 18 +++---- examples/chat/src/storage/message.rs | 18 +++---- src/es/adapter/mod.rs | 2 +- src/es/adapter/transformer/mod.rs | 2 +- 14 files changed, 123 insertions(+), 195 deletions(-) delete mode 100644 core/src/es/adapter/transformer/strategy/initialized.rs diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index f635339..041d6e2 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -13,7 +13,7 @@ use pin_project::pin_project; use ref_cast::RefCast; #[doc(inline)] -pub use self::transformer::{strategy, Strategy, Transformer, WithStrategy}; +pub use self::transformer::{strategy, Strategy, Transformer, Adapt}; /// Specifies result of [`Adapter`]. pub trait Returning { @@ -89,15 +89,15 @@ pub trait Returning { /// type Transformed = FileDomainEvent; /// } /// -/// impl adapter::WithStrategy for Adapter { +/// impl adapter::Adapt for Adapter { /// type Strategy = strategy::AsIs; /// } /// -/// impl adapter::WithStrategy for Adapter { +/// impl adapter::Adapt for Adapter { /// type Strategy = strategy::Into; /// } /// -/// impl adapter::WithStrategy for Adapter { +/// impl adapter::Adapt for Adapter { /// type Strategy = strategy::Skip; /// } /// @@ -176,10 +176,12 @@ where A: Returning, Ctx: ?Sized, Events: Stream, - Wrapper: Transformer, - A::Transformed: - From< as Transformer>::Transformed>, - A::Error: From< as Transformer>::Error>, + Wrapper: for<'c> Transformer<'c, Events::Item, Ctx>, + A::Transformed: for<'c> From< + as Transformer<'c, Events::Item, Ctx>>::Transformed, + >, + A::Error: + for<'c> From< as Transformer<'c, Events::Item, Ctx>>::Error>, { type Error = ::Error; type Transformed = ::Transformed; @@ -223,7 +225,7 @@ where #[pin_project] pub struct TransformedStream<'out, Adapter, Events, Ctx> where - Adapter: Transformer, + Adapter: Transformer<'out, Events::Item, Ctx>, Ctx: ?Sized, Events: Stream, { @@ -239,7 +241,7 @@ where impl<'out, Adapter, Events, Ctx> Debug for TransformedStream<'out, Adapter, Events, Ctx> where - Adapter: Debug + Transformer, + Adapter: Debug + Transformer<'out, Events::Item, Ctx>, Ctx: Debug + ?Sized, Events: Debug + Stream, { @@ -253,18 +255,18 @@ where } type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< - >::TransformedStream<'out>, + >::TransformedStream<'out>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> where - Adapter: Transformer, + Adapter: for<'c> Transformer<'c, Events::Item, Ctx>, Ctx: ?Sized, Events: Stream, { @@ -282,12 +284,12 @@ impl<'out, Adapter, Events, Ctx> Stream for TransformedStream<'out, Adapter, Events, Ctx> where Ctx: ?Sized, - Adapter: Transformer + Returning, + Adapter: Transformer<'out, Events::Item, Ctx> + Returning, Events: Stream, ::Transformed: - From<>::Transformed>, + From<>::Transformed>, ::Error: - From<>::Error>, + From<>::Error>, { type Item = Result< ::Transformed, diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 4e0fc91..7a4262b 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -15,7 +15,7 @@ pub use strategy::Strategy; /// [`Event`]: crate::es::Event /// [`Returning`]: super::Returning /// [`VersionedEvent`]: crate::es::VersionedEvent -pub trait WithStrategy { +pub trait Adapt { /// [`Strategy`] to transform [`Event`] with. /// /// [`Event`]: crate::es::Event @@ -40,7 +40,7 @@ pub trait WithStrategy { /// [`Skip`]: strategy::Skip /// [`Split`]: strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Transformer { +pub trait Transformer<'ctx, Event, Ctx: ?Sized> { /// Error of this [`Transformer`]. type Error; @@ -55,8 +55,8 @@ pub trait Transformer { /// [`Transformed`]: Self::Transformed type TransformedStream<'out>: Stream< Item = Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, > + 'out; @@ -64,7 +64,7 @@ pub trait Transformer { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, event: Event, context: &'ctx Ctx, diff --git a/core/src/es/adapter/transformer/strategy/as_is.rs b/core/src/es/adapter/transformer/strategy/as_is.rs index 2aca067..ece815c 100644 --- a/core/src/es/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/adapter/transformer/strategy/as_is.rs @@ -12,27 +12,23 @@ use super::Strategy; #[derive(Clone, Copy, Debug)] pub struct AsIs; -impl Strategy for AsIs +impl Strategy for AsIs where Adapter: adapter::Returning, Adapter::Error: 'static, - Ctx: ?Sized, Event: event::VersionedOrRaw + 'static, { + type Context = (); type Error = Adapter::Error; type Transformed = Event; - type TransformedStream<'out> = + type TransformedStream<'o> = stream::Once>>; - fn transform<'me, 'ctx, 'out>( - _: &'me Adapter, + fn transform<'me: 'out, 'ctx: 'out, 'out>( + _: &Adapter, event: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + _: &Self::Context, + ) -> Self::TransformedStream<'out> { stream::once(future::ready(Ok(event))) } } diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index ba85072..7dbdfcd 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -14,11 +14,9 @@ pub struct Custom; /// [`Strategy`]. /// /// [`Transformed`]: Self::Transformed -pub trait Customize -where - Event: event::VersionedOrRaw, - Ctx: ?Sized, -{ +pub trait Customize { + type Context: ?Sized; + /// Error of this [`Strategy`]. type Error; @@ -33,8 +31,8 @@ where /// [`Transformed`]: Self::Transformed type TransformedStream<'out>: Stream< Item = Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, > + 'out; @@ -42,34 +40,28 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( + fn transform<'me: 'out, 'ctx: 'out, 'out>( &'me self, event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out; + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'out>; } -impl Strategy for Custom +impl Strategy for Custom where - Adapter: Customize, + Adapter: Customize, Event: event::VersionedOrRaw, { + type Context = >::Context; type Error = Adapter::Error; type Transformed = Adapter::Transformed; type TransformedStream<'out> = Adapter::TransformedStream<'out>; - fn transform<'me, 'ctx, 'out>( + fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> { Adapter::transform(adapter, event, context) } } diff --git a/core/src/es/adapter/transformer/strategy/initialized.rs b/core/src/es/adapter/transformer/strategy/initialized.rs deleted file mode 100644 index 4b7b4da..0000000 --- a/core/src/es/adapter/transformer/strategy/initialized.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! [`Initialized`] [`Strategy`] definition. - -use std::marker::PhantomData; - -use futures::{stream, TryStreamExt as _}; - -use crate::es::event; - -use super::{AsIs, Strategy}; - -/// [`Strategy`] for wrapping [`Event`]s in [`Initial`]. -/// -/// [`Event`]: crate::es::Event -/// [`Initial`]: event::Initial -#[derive(Clone, Debug)] -pub struct Initialized(PhantomData); - -impl Strategy - for Initialized -where - Ctx: ?Sized, - Event: event::VersionedOrRaw, - InnerStrategy: Strategy, - InnerStrategy::Transformed: 'static, - InnerStrategy::Error: 'static, -{ - type Error = InnerStrategy::Error; - type Transformed = event::Initial; - type TransformedStream<'out> = stream::MapOk< - InnerStrategy::TransformedStream<'out>, - WrapInitial, - >; - - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, - event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { - InnerStrategy::transform(adapter, event, context).map_ok(event::Initial) - } -} - -type WrapInitial = fn(Event) -> event::Initial; diff --git a/core/src/es/adapter/transformer/strategy/into.rs b/core/src/es/adapter/transformer/strategy/into.rs index c0014f7..54ec383 100644 --- a/core/src/es/adapter/transformer/strategy/into.rs +++ b/core/src/es/adapter/transformer/strategy/into.rs @@ -14,16 +14,16 @@ use super::{AsIs, Strategy}; #[derive(Copy, Clone, Debug)] pub struct Into(PhantomData<(I, InnerStrategy)>); -impl - Strategy for Into +impl Strategy + for Into where - Ctx: ?Sized, Event: event::VersionedOrRaw, - InnerStrategy: Strategy, + InnerStrategy: Strategy, InnerStrategy::Transformed: 'static, InnerStrategy::Error: 'static, IntoEvent: From + 'static, { + type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; type TransformedStream<'out> = stream::MapOk< @@ -31,15 +31,11 @@ where IntoFn, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, event: Event, - ctx: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + ctx: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> { InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } } diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index 4ee1d71..06fd858 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -2,22 +2,22 @@ pub mod as_is; pub mod custom; -pub mod initialized; pub mod into; pub mod skip; pub mod split; +use std::borrow::Borrow; + use futures::Stream; use crate::es::{adapter, event}; -use super::{Transformer, WithStrategy}; +use super::{Transformer, Adapt}; #[doc(inline)] pub use self::{ as_is::AsIs, custom::{Custom, Customize}, - initialized::Initialized, into::Into, skip::Skip, split::{Split, Splitter}, @@ -26,10 +26,9 @@ pub use self::{ /// Generalized [`Transformer`] for [`Versioned`] events. /// /// [`Versioned`]: event::Versioned -pub trait Strategy -where - Ctx: ?Sized, -{ +pub trait Strategy { + type Context: ?Sized; + /// Error of this [`Strategy`]. type Error; @@ -44,8 +43,8 @@ where /// [`Transformed`]: Self::Transformed type TransformedStream<'out>: Stream< Item = Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, > + 'out; @@ -53,37 +52,36 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform<'me, 'ctx, 'out>( + fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, event: Event, - context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out; + context: &'ctx Self::Context, + ) -> Self::TransformedStream<'out>; } -impl Transformer for adapter::Wrapper +impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> + for adapter::Wrapper where - Ctx: ?Sized, Event: event::VersionedOrRaw, - Adapter: WithStrategy + adapter::Returning, - Adapter::Strategy: Strategy, + Adapter: Adapt + adapter::Returning, + Adapter::Strategy: Strategy, Adapter::Transformed: - From<>::Transformed>, + From<>::Transformed>, Adapter::Error: - From<>::Error>, + From<>::Error>, + Ctx: Borrow<>::Context> + + ?Sized, + >::Context: 'ctx, { - type Error = >::Error; + type Error = >::Error; type Transformed = - >::Transformed; + >::Transformed; type TransformedStream<'out> = >::TransformedStream<'out>; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, event: Event, context: &'ctx Ctx, @@ -92,8 +90,10 @@ where 'me: 'out, 'ctx: 'out, { - >::transform( - &self.0, event, context, + >::transform( + &self.0, + event, + context.borrow(), ) } } diff --git a/core/src/es/adapter/transformer/strategy/skip.rs b/core/src/es/adapter/transformer/strategy/skip.rs index e96bda2..40d64a6 100644 --- a/core/src/es/adapter/transformer/strategy/skip.rs +++ b/core/src/es/adapter/transformer/strategy/skip.rs @@ -12,28 +12,24 @@ use super::Strategy; #[derive(Clone, Copy, Debug)] pub struct Skip; -impl Strategy for Skip +impl Strategy for Skip where - Ctx: ?Sized, Event: event::VersionedOrRaw, Adapter: adapter::Returning, Adapter::Transformed: 'static, Adapter::Error: 'static, { + type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out> = + type TransformedStream<'o> = stream::Empty>; - fn transform<'me, 'ctx, 'out>( - _: &'me Adapter, + fn transform<'me: 'out, 'ctx: 'out, 'out>( + _: &Adapter, _: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + _: &Self::Context, + ) -> Self::TransformedStream<'out> { stream::empty() } } diff --git a/core/src/es/adapter/transformer/strategy/split.rs b/core/src/es/adapter/transformer/strategy/split.rs index d8fae85..42aae57 100644 --- a/core/src/es/adapter/transformer/strategy/split.rs +++ b/core/src/es/adapter/transformer/strategy/split.rs @@ -30,29 +30,24 @@ pub trait Splitter { fn split(&self, event: From) -> Self::Iterator; } -impl Strategy - for Split +impl Strategy for Split where Adapter: Splitter + adapter::Returning, Adapter::Iterator: 'static, Adapter::Error: 'static, - Ctx: ?Sized, Event: event::VersionedOrRaw, IntoEvent: 'static, { + type Context = (); type Error = Adapter::Error; type Transformed = ::Item; - type TransformedStream<'out> = SplitStream; + type TransformedStream<'o> = SplitStream; - fn transform<'me, 'ctx, 'out>( - adapter: &'me Adapter, + fn transform<'me: 'out, 'ctx: 'out, 'out>( + adapter: &Adapter, event: Event, - _: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + _: &Self::Context, + ) -> Self::TransformedStream<'out> { stream::iter(adapter.split(event)).map(Ok) } } diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 019e083..41f2512 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,6 +1,6 @@ use std::convert::Infallible; -use arcana::es::adapter::{self, strategy, WithStrategy}; +use arcana::es::adapter::{self, strategy, Adapt}; use crate::event; @@ -12,39 +12,37 @@ impl adapter::Returning for Adapter { #[derive(Clone, Copy, Debug)] pub struct Adapter; -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Initialized; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Initialized; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Initialized>; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::AsIs; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl - WithStrategy< - event::Raw, - > for Adapter +impl Adapt> + for Adapter { type Strategy = strategy::Skip; } diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 53bc0c5..9f7650f 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,7 +1,7 @@ use std::{array, iter}; use arcana::es::{ - adapter::{self, strategy, strategy::Splitter, WithStrategy}, + adapter::{self, strategy, strategy::Splitter, Adapt}, event::Initial, }; use either::Either; @@ -17,37 +17,37 @@ impl adapter::Returning for Adapter { #[derive(Clone, Copy, Debug)] pub struct Adapter; -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Initialized; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Split>; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } impl - WithStrategy< + Adapt< event::Raw, > for Adapter { diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 2ab2549..8cc4caa 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,6 +1,6 @@ use std::convert::Infallible; -use arcana::es::adapter::{self, strategy, WithStrategy}; +use arcana::es::adapter::{self, strategy, Adapt}; use futures::stream; use crate::event; @@ -13,36 +13,36 @@ impl adapter::Returning for Adapter { #[derive(Debug)] pub struct Adapter; -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Initialized; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Custom; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl WithStrategy for Adapter { +impl Adapt for Adapter { type Strategy = strategy::Skip; } impl - WithStrategy< + Adapt< event::Raw, > for Adapter { diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index ea0f08f..812b708 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,6 +7,6 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{ - strategy, Adapter, Returning, Strategy, TransformedStream, WithStrategy, + strategy, Adapter, Returning, Strategy, TransformedStream, Adapt, Wrapper, }; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index f14db38..62a68c8 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -6,4 +6,4 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{Transformer, WithStrategy}; +pub use arcana_core::es::adapter::transformer::{Transformer, Adapt}; From c39af00b498ab5ca1052202770e569fe9598ced9 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 12 Oct 2021 14:32:34 +0300 Subject: [PATCH 078/104] Make it work again --- codegen/impl/src/es/event/mod.rs | 36 ++++----- core/src/es/adapter/mod.rs | 76 ++++++++++--------- .../es/adapter/transformer/strategy/custom.rs | 1 + .../es/adapter/transformer/strategy/mod.rs | 3 +- core/src/es/event.rs | 2 + core/src/es/mod.rs | 4 +- examples/chat/src/event/mod.rs | 12 ++- examples/chat/src/storage/chat.rs | 13 ++-- examples/chat/src/storage/email.rs | 33 ++++---- examples/chat/src/storage/message.rs | 28 ++++--- examples/chat/src/storage/mod.rs | 21 +++-- src/es/adapter/transformer/strategy.rs | 2 +- src/es/mod.rs | 4 +- 13 files changed, 129 insertions(+), 106 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index eff17e9..571740f 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -383,7 +383,7 @@ impl Definition { quote! { #[automatically_derived] impl#impl_gen ::arcana::es::adapter::Transformer< - #event#type_gen, __Ctx + '__ctx, #event#type_gen, __Ctx > for ::arcana::es::adapter::Wrapper<__A> #where_clause { type Error = <__A as ::arcana::es::adapter::Returning>:: @@ -392,16 +392,16 @@ impl Definition { Transformed; type TransformedStream<'out> = #transformed; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, __event: #event#type_gen, - __context: &'ctx __Ctx, + __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, #event#type_gen, __Ctx>>:: TransformedStream<'out> where 'me: 'out, - 'ctx: 'out, + '__ctx: 'out, { match __event { #inner_match @@ -427,7 +427,7 @@ impl Definition { syn::GenericParam, syn::Token![,], > = parse_quote! { - __A, __Ctx + '__ctx, __A, __Ctx }; let transformer_bounds: Punctuated< syn::WherePredicate, @@ -435,24 +435,24 @@ impl Definition { > = parse_quote! { __A: ::arcana::es::adapter::Returning, Self: #( - ::arcana::es::adapter::Transformer<#var_type, __Ctx> + ::arcana::es::adapter::Transformer<'__ctx, #var_type, __Ctx> )+*, <__A as ::arcana::es::adapter::Returning>::Transformed: 'static #( + ::std::convert::From<>::Transformed> + Transformer<'__ctx, #var_type, __Ctx>>::Transformed> )*, <__A as ::arcana::es::adapter::Returning>::Error: 'static #( + ::std::convert::From<>::Error> + Transformer<'__ctx, #var_type, __Ctx>>::Error> )*, #( - >:: + >:: Transformed: 'static, - >:: + >:: Error: 'static, )* }; @@ -488,20 +488,20 @@ impl Definition { quote! { ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, + Transformer<'__ctx, #from, __Ctx>>::Transformed, >::Error, + Transformer<'__ctx, #from, __Ctx>>::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, #event#ty_gen, __Ctx>>::Transformed, >::Error, + Transformer<'__ctx, #event#ty_gen, __Ctx>>::Error, > > } @@ -548,14 +548,14 @@ impl Definition { self.variants .iter() .filter_map(|var| { - var.fields.iter().next().map(|f| (&var.ident, &f.ty)) + var.0.fields.iter().next().map(|f| (&var.0.ident, &f.ty)) }) .enumerate() .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { ::arcana::es::event::codegen::futures::StreamExt::map( >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = |__res| { diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 041d6e2..3be2ff1 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -13,7 +13,7 @@ use pin_project::pin_project; use ref_cast::RefCast; #[doc(inline)] -pub use self::transformer::{strategy, Strategy, Transformer, Adapt}; +pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; /// Specifies result of [`Adapter`]. pub trait Returning { @@ -133,7 +133,10 @@ pub trait Returning { /// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version /// [`VersionedEvent`]: crate::es::VersionedEvent -pub trait Adapter { +pub trait Adapter<'ctx, Events, Ctx> +where + Ctx: ?Sized + 'ctx, +{ /// Error of this [`Adapter`]. type Error; @@ -148,11 +151,12 @@ pub trait Adapter { /// [`Transformed`]: Self::Transformed type TransformedStream<'out>: Stream< Item = Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, > + 'out where + 'ctx: 'out, Ctx: 'out, Events: 'out, Self: 'out; @@ -161,7 +165,7 @@ pub trait Adapter { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me, 'ctx, 'out>( + fn transform_all<'me, 'out>( &'me self, events: Events, context: &'ctx Ctx, @@ -171,28 +175,27 @@ pub trait Adapter { 'ctx: 'out; } -impl Adapter for A +impl<'ctx, A, Events, Ctx> Adapter<'ctx, Events, Ctx> for A where A: Returning, - Ctx: ?Sized, + Ctx: ?Sized + 'ctx, Events: Stream, - Wrapper: for<'c> Transformer<'c, Events::Item, Ctx>, - A::Transformed: for<'c> From< - as Transformer<'c, Events::Item, Ctx>>::Transformed, - >, - A::Error: - for<'c> From< as Transformer<'c, Events::Item, Ctx>>::Error>, + Wrapper: Transformer<'ctx, Events::Item, Ctx>, + A::Transformed: + From< as Transformer<'ctx, Events::Item, Ctx>>::Transformed>, + A::Error: From< as Transformer<'ctx, Events::Item, Ctx>>::Error>, { type Error = ::Error; type Transformed = ::Transformed; type TransformedStream<'out> where + 'ctx: 'out, Ctx: 'out, Events: 'out, Self: 'out, - = TransformedStream<'out, Wrapper, Events, Ctx>; + = TransformedStream<'ctx, 'out, Wrapper, Events, Ctx>; - fn transform_all<'me, 'ctx, 'out>( + fn transform_all<'me, 'out>( &'me self, events: Events, context: &'ctx Ctx, @@ -223,9 +226,9 @@ where /// [`Stream`] for [`Adapter`] blanket impl. #[pin_project] -pub struct TransformedStream<'out, Adapter, Events, Ctx> +pub struct TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - Adapter: Transformer<'out, Events::Item, Ctx>, + Adapter: Transformer<'ctx, Events::Item, Ctx>, Ctx: ?Sized, Events: Stream, { @@ -233,15 +236,15 @@ where events: Events, #[pin] transformed_stream: - AdapterTransformedStream<'out, Events::Item, Adapter, Ctx>, + AdapterTransformedStream<'ctx, 'out, Events::Item, Adapter, Ctx>, adapter: &'out Adapter, - context: &'out Ctx, + context: &'ctx Ctx, } -impl<'out, Adapter, Events, Ctx> Debug - for TransformedStream<'out, Adapter, Events, Ctx> +impl<'ctx, 'out, Adapter, Events, Ctx> Debug + for TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - Adapter: Debug + Transformer<'out, Events::Item, Ctx>, + Adapter: Debug + Transformer<'ctx, Events::Item, Ctx>, Ctx: Debug + ?Sized, Events: Debug + Stream, { @@ -254,23 +257,27 @@ where } } -type AdapterTransformedStream<'out, Event, Adapter, Ctx> = future::Either< - >::TransformedStream<'out>, +type AdapterTransformedStream<'ctx, 'out, Event, Adapter, Ctx> = future::Either< + >::TransformedStream<'out>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; -impl<'out, Adapter, Events, Ctx> TransformedStream<'out, Adapter, Events, Ctx> +impl<'ctx, 'out, Adapter, Events, Ctx> + TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - Adapter: for<'c> Transformer<'c, Events::Item, Ctx>, + Adapter: Transformer<'ctx, Events::Item, Ctx>, Ctx: ?Sized, Events: Stream, { - fn new(adapter: &'out Adapter, events: Events, context: &'out Ctx) -> Self { + fn new(adapter: &'out Adapter, events: Events, context: &'ctx Ctx) -> Self + where + 'ctx: 'out, + { Self { events, transformed_stream: stream::empty().right_stream(), @@ -280,16 +287,17 @@ where } } -impl<'out, Adapter, Events, Ctx> Stream - for TransformedStream<'out, Adapter, Events, Ctx> +impl<'ctx, 'out, Adapter, Events, Ctx> Stream + for TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where + 'ctx: 'out, Ctx: ?Sized, - Adapter: Transformer<'out, Events::Item, Ctx> + Returning, + Adapter: Transformer<'ctx, Events::Item, Ctx> + Returning, Events: Stream, ::Transformed: - From<>::Transformed>, + From<>::Transformed>, ::Error: - From<>::Error>, + From<>::Error>, { type Item = Result< ::Transformed, diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index 7dbdfcd..4a4e83a 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -15,6 +15,7 @@ pub struct Custom; /// /// [`Transformed`]: Self::Transformed pub trait Customize { + /// TODO type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index 06fd858..3edf63a 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -12,7 +12,7 @@ use futures::Stream; use crate::es::{adapter, event}; -use super::{Transformer, Adapt}; +use super::{Adapt, Transformer}; #[doc(inline)] pub use self::{ @@ -27,6 +27,7 @@ pub use self::{ /// /// [`Versioned`]: event::Versioned pub trait Strategy { + /// TODO type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 8777e07..63fd9a3 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -297,6 +297,8 @@ pub mod codegen { //! //! [`Event`]: super::Event + use super::Raw; + pub use futures; impl Raw { diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index 9e27988..e97c1f9 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -10,7 +10,7 @@ pub use self::adapter::Adapter; #[doc(inline)] pub use self::event::{ - Event, Initialized as EventInitialized, Name as EventName, + Event, Initialized as EventInitialized, Name as EventName, Raw as RawEvent, Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, Raw as RawEvent, + Version as EventVersion, Versioned as VersionedEvent, }; diff --git a/examples/chat/src/event/mod.rs b/examples/chat/src/event/mod.rs index 9c1245b..ff448b9 100644 --- a/examples/chat/src/event/mod.rs +++ b/examples/chat/src/event/mod.rs @@ -9,20 +9,24 @@ pub use event::{Initial, Raw, Version}; #[derive(Debug, Event, From, PartialEq)] pub enum Chat { - PrivateCreated(event::Initial), - PublicCreated(event::Initial), + #[event(init)] + PrivateCreated(chat::private::Created), + #[event(init)] + PublicCreated(chat::public::Created), MessagePosted(message::Posted), } #[derive(Debug, Event, From, PartialEq)] pub enum Email { - Added(event::Initial), + #[event(init)] + Added(email::Added), Confirmed(email::Confirmed), } #[derive(Debug, Event, From, PartialEq)] pub enum Message { - MessagePosted(event::Initial), + #[event(init)] + MessagePosted(message::Posted), } #[cfg(test)] diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 41f2512..3cde0ba 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -13,22 +13,21 @@ impl adapter::Returning for Adapter { pub struct Adapter; impl Adapt for Adapter { - type Strategy = strategy::Initialized; + type Strategy = strategy::AsIs; } impl Adapt for Adapter { - type Strategy = strategy::Initialized; -} - -impl Adapt for Adapter { - type Strategy = - strategy::Initialized>; + type Strategy = strategy::AsIs; } impl Adapt for Adapter { type Strategy = strategy::AsIs; } +impl Adapt for Adapter { + type Strategy = strategy::Into; +} + impl Adapt for Adapter { type Strategy = strategy::Skip; } diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 9f7650f..06a5fbf 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,9 +1,6 @@ -use std::{array, iter}; +use std::{array, borrow::Borrow, iter}; -use arcana::es::{ - adapter::{self, strategy, strategy::Splitter, Adapt}, - event::Initial, -}; +use arcana::es::adapter::{self, strategy, strategy::Splitter, Adapt}; use either::Either; use futures::{future, stream, StreamExt as _}; @@ -18,7 +15,7 @@ impl adapter::Returning for Adapter { pub struct Adapter; impl Adapt for Adapter { - type Strategy = strategy::Initialized; + type Strategy = strategy::AsIs; } impl Adapt for Adapter { @@ -46,10 +43,8 @@ impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl - Adapt< - event::Raw, - > for Adapter +impl Adapt> + for Adapter { type Strategy = strategy::Custom; } @@ -87,12 +82,12 @@ type SplitEmail = Either< array::IntoIter, 2>, >; -impl +impl strategy::Customize< event::Raw, - Ctx, > for Adapter { + type Context = dyn Bound; type Error = serde_json::Error; type Transformed = Either; type TransformedStream<'out> = CustomizedStream; @@ -103,7 +98,7 @@ impl event::email::v2::AddedAndConfirmed, serde_json::Value, >, - _context: &'ctx Ctx, + _context: &'ctx Self::Context, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -148,8 +143,18 @@ impl From> { fn from(ev: Either) -> Self { match ev { - Either::Left(ev) => Initial(ev).into(), + Either::Left(ev) => ev.into(), Either::Right(ev) => ev.into(), } } } + +pub trait Bound {} + +impl Bound for () {} + +impl Borrow<(dyn Bound + 'static)> for () { + fn borrow(&self) -> &(dyn Bound + 'static) { + self + } +} diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 8cc4caa..4f710e0 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,4 +1,4 @@ -use std::convert::Infallible; +use std::{borrow::Borrow, convert::Infallible}; use arcana::es::adapter::{self, strategy, Adapt}; use futures::stream; @@ -14,7 +14,7 @@ impl adapter::Returning for Adapter { pub struct Adapter; impl Adapt for Adapter { - type Strategy = strategy::Initialized; + type Strategy = strategy::AsIs; } impl Adapt for Adapter { @@ -41,19 +41,15 @@ impl Adapt for Adapter { type Strategy = strategy::Skip; } -impl - Adapt< - event::Raw, - > for Adapter +impl Adapt> + for Adapter { type Strategy = strategy::Skip; } // Basically same as Skip, but with additional Ctx bounds -impl strategy::Customize for Adapter -where - Ctx: From, -{ +impl strategy::Customize for Adapter { + type Context = dyn Bound; type Error = Infallible; type Transformed = event::Message; type TransformedStream<'out> = @@ -62,7 +58,7 @@ where fn transform<'me, 'ctx, 'out>( &'me self, _event: event::chat::public::Created, - _context: &'ctx Ctx, + _context: &'ctx Self::Context, ) -> Self::TransformedStream<'out> where 'me: 'out, @@ -71,3 +67,13 @@ where stream::empty() } } + +pub trait Bound {} + +impl Bound for () {} + +impl Borrow<(dyn Bound + 'static)> for () { + fn borrow(&self) -> &(dyn Bound + 'static) { + self + } +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index ad361b2..bbc8958 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -64,9 +64,9 @@ mod spec { assert_eq!( chat_events, vec![ - event::Initial(event::chat::private::Created).into(), - event::Initial(event::chat::private::Created).into(), - event::Initial(event::chat::public::Created).into(), + event::chat::private::Created.into(), + event::chat::private::Created.into(), + event::chat::public::Created.into(), event::message::Posted.into() ] ); @@ -93,13 +93,13 @@ mod spec { assert_eq!( email_events, vec![ - event::Initial(event::email::Added { + event::email::Added { email: "hello@world.com".to_string() - }) + } .into(), - event::Initial(event::email::Added { + event::email::Added { email: "raw@event.com".to_string() - }) + } .into(), event::email::Confirmed { confirmed_by: "User".to_string() @@ -121,16 +121,13 @@ mod spec { async fn message_adapter() { let mut message = Option::::None; let message_events = message::Adapter - .transform_all(incoming_events(), &1) + .transform_all(incoming_events(), &()) .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await .unwrap(); - assert_eq!( - message_events, - vec![event::Initial(event::message::Posted).into()], - ); + assert_eq!(message_events, vec![event::message::Posted.into()],); assert_eq!(message, Some(domain::Message)); } diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index 83da2db..7fdad58 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,5 +2,5 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Custom, Customize, Initialized, Into, Skip, Split, Splitter, Strategy, + AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, }; diff --git a/src/es/mod.rs b/src/es/mod.rs index dbc831b..e97c1f9 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -10,7 +10,7 @@ pub use self::adapter::Adapter; #[doc(inline)] pub use self::event::{ - Event, Initialized as EventInitialized, Name as EventName, + Event, Initialized as EventInitialized, Name as EventName, Raw as RawEvent, Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, Raw as RawEvent, + Version as EventVersion, Versioned as VersionedEvent, }; From f04130eb99c40d9d6b20b758d6b9dd766704ecf0 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 12 Oct 2021 15:31:09 +0300 Subject: [PATCH 079/104] Add test for deserialization error --- examples/chat/src/storage/mod.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index bbc8958..9c79acb 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -41,7 +41,7 @@ mod spec { use std::array; use arcana::es::{Adapter as _, EventSourced as _}; - use futures::{stream, Stream, TryStreamExt as _}; + use futures::{future, stream, Stream, TryStreamExt as _}; use serde_json::json; use crate::domain; @@ -57,7 +57,7 @@ mod spec { let chat_events = chat::Adapter .transform_all(incoming_events(), &()) .inspect_ok(|ev| chat.apply(ev)) - .try_collect::>() + .try_collect::>() .await .unwrap(); @@ -86,7 +86,7 @@ mod spec { let email_events = email::Adapter .transform_all(incoming_events(), &()) .inspect_ok(|ev| email.apply(ev)) - .try_collect::>() + .try_collect::>() .await .unwrap(); @@ -116,6 +116,23 @@ mod spec { ); } + #[allow(clippy::semicolon_if_nothing_returned)] + #[tokio::test] + async fn email_adapter_with_corrupted_event() { + let corrupted_event = + Event::from(EmailEvent::RawAddedAndConfirmed(event::Raw::new( + json!({ "corrupted": "raw@event.com", "confirmed_by": "User" }), + event::Version::try_new(1).unwrap(), + ))); + + let result = email::Adapter + .transform_all(stream::once(future::ready(corrupted_event)), &()) + .try_collect::>() + .await; + + assert_eq!(result.unwrap_err().to_string(), "missing field `email`") + } + #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn message_adapter() { From fe6aa3da5bc6e7bdfbfef500cdd55c6117e08106 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 13 Oct 2021 13:19:07 +0300 Subject: [PATCH 080/104] Change lifetimes a bit [skip ci] --- core/src/es/adapter/mod.rs | 24 +++++++----------------- examples/chat/src/storage/mod.rs | 2 +- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 3be2ff1..decc905 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -133,10 +133,7 @@ pub trait Returning { /// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version /// [`VersionedEvent`]: crate::es::VersionedEvent -pub trait Adapter<'ctx, Events, Ctx> -where - Ctx: ?Sized + 'ctx, -{ +pub trait Adapter<'ctx, Events, Ctx: ?Sized> { /// Error of this [`Adapter`]. type Error; @@ -157,7 +154,7 @@ where > + 'out where 'ctx: 'out, - Ctx: 'out, + Ctx: 'ctx, Events: 'out, Self: 'out; @@ -165,14 +162,11 @@ where /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me, 'out>( + fn transform_all<'me: 'out, 'out>( &'me self, events: Events, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out; + ) -> Self::TransformedStream<'out>; } impl<'ctx, A, Events, Ctx> Adapter<'ctx, Events, Ctx> for A @@ -190,20 +184,16 @@ where type TransformedStream<'out> where 'ctx: 'out, - Ctx: 'out, + Ctx: 'ctx, Events: 'out, Self: 'out, = TransformedStream<'ctx, 'out, Wrapper, Events, Ctx>; - fn transform_all<'me, 'out>( + fn transform_all<'me: 'out, 'out>( &'me self, events: Events, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> - where - 'me: 'out, - 'ctx: 'out, - { + ) -> Self::TransformedStream<'out> { TransformedStream::new(RefCast::ref_cast(self), events, context) } } diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 9c79acb..730c998 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -144,7 +144,7 @@ mod spec { .await .unwrap(); - assert_eq!(message_events, vec![event::message::Posted.into()],); + assert_eq!(message_events, vec![event::message::Posted.into()]); assert_eq!(message, Some(domain::Message)); } From 2c586294cfc055f420348603ab588ad6560bb93f Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 13 Oct 2021 15:08:55 +0300 Subject: [PATCH 081/104] Fix proc macro tests --- codegen/impl/src/es/event/mod.rs | 251 ++++++++++++++++++------------ examples/event.rs | 85 ---------- src/es/adapter/mod.rs | 3 +- src/es/adapter/transformer/mod.rs | 2 +- 4 files changed, 153 insertions(+), 188 deletions(-) delete mode 100644 examples/event.rs diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 571740f..b68bf53 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -450,10 +450,12 @@ impl Definition { Transformer<'__ctx, #var_type, __Ctx>>::Error> )*, #( - >:: - Transformed: 'static, - >:: - Error: 'static, + >::Transformed: 'static, + >::Error: 'static, )* }; @@ -493,13 +495,15 @@ impl Definition { fn( ::std::result::Result< >::Transformed, + Transformer<'__ctx, #from, __Ctx>>:: + Transformed, >::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, #event#ty_gen, __Ctx>>:: + Transformed, >::Error, > @@ -659,33 +663,42 @@ mod spec { } #[automatically_derived] - impl<__A, __Ctx> ::arcana::es::adapter::Transformer - for ::arcana::es::adapter::Wrapper<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::adapter::Wrapper<__A> where __A: ::arcana::es::adapter::Returning, Self: - ::arcana::es::adapter::Transformer + - ::arcana::es::adapter::Transformer, + ::arcana::es::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + >, <__A as ::arcana::es::adapter::Returning>::Transformed: 'static + ::std::convert::From< >::Transformed> + + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed> + ::std::convert::From< >::Transformed>, + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: 'static + ::std::convert::From< >::Error> + + Transformer<'__ctx, FileEvent, __Ctx> >::Error> + ::std::convert::From< >::Error>, + Transformer<'__ctx, ChatEvent, __Ctx> >::Error>, >::Transformed: 'static, + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed: + 'static, >::Error: 'static, + Transformer<'__ctx, FileEvent, __Ctx> >::Error: + 'static, >::Transformed: 'static, + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed: + 'static, >::Error: 'static + Transformer<'__ctx, ChatEvent, __Ctx> >::Error: + 'static { type Error = <__A as ::arcana::es::adapter::Returning>:: Error; @@ -695,58 +708,64 @@ mod spec { ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >:: - Transformed, + Transformer< + '__ctx, FileEvent, __Ctx + >>::Transformed, >:: - Error, + Transformer< + '__ctx, FileEvent, __Ctx + >>::Error, >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, Event, __Ctx>>:: Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >:: - Transformed, + Transformer< + '__ctx, ChatEvent, __Ctx + >>::Transformed, >:: - Error, + Transformer< + '__ctx, ChatEvent, __Ctx + >>::Error, >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, Event, __Ctx>>:: Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, __event: Event, - __context: &'ctx __Ctx, + __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, Event, __Ctx>>:: TransformedStream<'out> where 'me: 'out, - 'ctx: 'out, + '__ctx: 'out, { match __event { Event::File(__event) => { @@ -755,7 +774,9 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( + Transformer< + '__ctx, FileEvent, __Ctx + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = @@ -779,7 +800,9 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( + Transformer< + '__ctx, ChatEvent, __Ctx + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = @@ -919,37 +942,46 @@ mod spec { } #[automatically_derived] - impl<'a, F, C, __A, __Ctx> ::arcana::es::adapter:: - Transformer, __Ctx> for + impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::adapter:: + Transformer<'__ctx, Event<'a, F, C>, __Ctx> for ::arcana::es::adapter::Wrapper<__A> where __A: ::arcana::es::adapter::Returning, Self: - ::arcana::es::adapter::Transformer, __Ctx> + - ::arcana::es::adapter::Transformer, __Ctx>, + ::arcana::es::adapter::Transformer< + '__ctx, FileEvent<'a, F>, __Ctx + > + + ::arcana::es::adapter::Transformer< + '__ctx, ChatEvent<'a, C>, __Ctx + >, <__A as ::arcana::es::adapter::Returning>::Transformed: 'static + ::std::convert::From< , __Ctx> >::Transformed> + + Transformer< + '__ctx, FileEvent<'a, F>, __Ctx> + >::Transformed> + ::std::convert::From< , __Ctx> >::Transformed>, + Transformer< + '__ctx, ChatEvent<'a, C>, __Ctx> + >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: 'static + ::std::convert::From< , __Ctx> >::Error> + + Transformer<'__ctx, FileEvent<'a, F>, __Ctx> >::Error> + ::std::convert::From< , __Ctx> >::Error>, + Transformer<'__ctx, ChatEvent<'a, C>, __Ctx> >::Error>, , __Ctx> >::Transformed: + Transformer<'__ctx, FileEvent<'a, F>, __Ctx> >::Transformed: 'static, , __Ctx> >::Error: + Transformer<'__ctx, FileEvent<'a, F>, __Ctx> >::Error: 'static, , __Ctx> >::Transformed: + Transformer<'__ctx, ChatEvent<'a, C>, __Ctx> >::Transformed: 'static, , __Ctx> >::Error: 'static + Transformer<'__ctx, ChatEvent<'a, C>, __Ctx> >::Error: + 'static { type Error = <__A as ::arcana::es::adapter::Returning>:: Error; @@ -959,66 +991,68 @@ mod spec { ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< , __Ctx + '__ctx, FileEvent<'a, F>, __Ctx >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx + '__ctx, FileEvent<'a, F>, __Ctx >>::Transformed, , __Ctx + '__ctx, FileEvent<'a, F>, __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx>>:: - Transformed, + Transformer< + '__ctx, Event<'a, F, C>, __Ctx + >>::Transformed, , __Ctx>>:: - Error, + Transformer< + '__ctx, Event<'a, F, C>, __Ctx + >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< , __Ctx + '__ctx, ChatEvent<'a, C>, __Ctx >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx + '__ctx, ChatEvent<'a, C>, __Ctx >>::Transformed, , __Ctx + '__ctx, ChatEvent<'a, C>, __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx + '__ctx, Event<'a, F, C>, __Ctx >>::Transformed, , __Ctx + '__ctx, Event<'a, F, C>, __Ctx >>::Error, > >, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, __event: Event<'a, F, C>, - __context: &'ctx __Ctx, + __context: &'__ctx __Ctx, ) -> , __Ctx>>:: + Transformer<'__ctx, Event<'a, F, C>, __Ctx>>:: TransformedStream<'out> where 'me: 'out, - 'ctx: 'out, + '__ctx: 'out, { match __event { Event::<'a, F, C>::File(__event) => { @@ -1027,7 +1061,7 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( , __Ctx + '__ctx, FileEvent<'a, F>, __Ctx > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = @@ -1051,7 +1085,7 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( , __Ctx + '__ctx, ChatEvent<'a, C>, __Ctx > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = @@ -1198,33 +1232,40 @@ mod spec { } #[automatically_derived] - impl<__A, __Ctx> ::arcana::es::adapter::Transformer - for ::arcana::es::adapter::Wrapper<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::adapter::Wrapper<__A> where __A: ::arcana::es::adapter::Returning, Self: - ::arcana::es::adapter::Transformer + - ::arcana::es::adapter::Transformer, + ::arcana::es::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + >, <__A as ::arcana::es::adapter::Returning>::Transformed: 'static + ::std::convert::From< >::Transformed> + + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed> + ::std::convert::From< >::Transformed>, + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed>, <__A as ::arcana::es::adapter::Returning>::Error: 'static + ::std::convert::From< >::Error> + + Transformer<'__ctx, FileEvent, __Ctx> >::Error> + ::std::convert::From< >::Error>, + Transformer<'__ctx, ChatEvent, __Ctx> >::Error>, >::Transformed: 'static, + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed: + 'static, >::Error: 'static, + Transformer<'__ctx, FileEvent, __Ctx> >::Error: 'static, >::Transformed: 'static, + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed: + 'static, >::Error: 'static + Transformer<'__ctx, ChatEvent, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::adapter::Returning>:: Error; @@ -1234,58 +1275,64 @@ mod spec { ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >:: - Transformed, + Transformer< + '__ctx, FileEvent, __Ctx + >>::Transformed, >:: - Error, + Transformer< + '__ctx, FileEvent, __Ctx + >>::Error, >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, Event, __Ctx>>:: Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, ::arcana::es::event::codegen::futures::stream::Map< >::TransformedStream<'out>, fn( ::std::result::Result< >:: - Transformed, + Transformer< + '__ctx, ChatEvent, __Ctx + >>::Transformed, >:: - Error, + Transformer< + '__ctx, ChatEvent, __Ctx + >>::Error, >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, Event, __Ctx>>:: Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, >; - fn transform<'me, 'ctx, 'out>( + fn transform<'me, 'out>( &'me self, __event: Event, - __context: &'ctx __Ctx, + __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, Event, __Ctx>>:: TransformedStream<'out> where 'me: 'out, - 'ctx: 'out, + '__ctx: 'out, { match __event { Event::File(__event) => { @@ -1294,7 +1341,9 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( + Transformer< + '__ctx, FileEvent, __Ctx + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = @@ -1318,7 +1367,9 @@ mod spec { ::arcana::es::event::codegen::futures:: StreamExt::map( + Transformer< + '__ctx, ChatEvent, __Ctx + > >::transform(self, __event, __context), { let __transform_fn: fn(_) -> _ = diff --git a/examples/event.rs b/examples/event.rs deleted file mode 100644 index f0350b7..0000000 --- a/examples/event.rs +++ /dev/null @@ -1,85 +0,0 @@ -use arcana::es::event::{self, Event, Initialized, Sourced, Sourcing}; - -#[derive(event::Versioned)] -#[event(name = "chat.created", version = 1)] -struct ChatCreated; - -#[derive(event::Versioned)] -#[event(name = "message.posted", version = 1)] -struct MessagePosted; - -#[derive(Event)] -enum ChatEvent { - #[event(init)] - Created(ChatCreated), - MessagePosted(MessagePosted), -} - -#[derive(Event)] -enum MessageEvent { - #[event(init)] - MessagePosted(MessagePosted), -} - -#[derive(Event)] -enum AnyEvent { - Chat(ChatEvent), - Message(MessageEvent), -} - -#[derive(Debug, Eq, PartialEq)] -struct Chat { - message_count: usize, -} - -impl Initialized for Chat { - fn init(_: &ChatCreated) -> Self { - Self { message_count: 0 } - } -} - -impl Sourced for Chat { - fn apply(&mut self, _: &MessagePosted) { - self.message_count += 1; - } -} - -#[derive(Debug, Eq, PartialEq)] -struct Message; - -impl Initialized for Message { - fn init(_: &MessagePosted) -> Self { - Self - } -} - -fn main() { - let mut chat = Option::::None; - let mut message = Option::::None; - - let ev = ChatEvent::Created(ChatCreated.into()); - chat.apply(&ev); - assert_eq!(ev.name(), "chat.created"); - assert_eq!(chat, Some(Chat { message_count: 0 })); - - let ev = ChatEvent::MessagePosted(MessagePosted); - chat.apply(&ev); - assert_eq!(ev.name(), "message.posted"); - assert_eq!(chat, Some(Chat { message_count: 1 })); - - let ev: &dyn Sourcing> = &ev; - chat.apply(ev); - assert_eq!(chat, Some(Chat { message_count: 2 })); - - let ev = MessageEvent::MessagePosted(MessagePosted.into()); - message.apply(&ev); - assert_eq!(ev.name(), "message.posted"); - assert_eq!(message, Some(Message)); - - let ev = AnyEvent::Chat(ChatEvent::Created(ChatCreated.into())); - assert_eq!(ev.name(), "chat.created"); - - let ev = - AnyEvent::Message(MessageEvent::MessagePosted(MessagePosted.into())); - assert_eq!(ev.name(), "message.posted"); -} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 812b708..07c1572 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -7,6 +7,5 @@ pub use self::transformer::Transformer; #[doc(inline)] pub use arcana_core::es::adapter::{ - strategy, Adapter, Returning, Strategy, TransformedStream, Adapt, - Wrapper, + strategy, Adapt, Adapter, Returning, Strategy, TransformedStream, Wrapper, }; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/adapter/transformer/mod.rs index 62a68c8..867d417 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/adapter/transformer/mod.rs @@ -6,4 +6,4 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{Transformer, Adapt}; +pub use arcana_core::es::adapter::transformer::{Adapt, Transformer}; From 879bae76df6093cfffd7c4c96bbd33c9779e9611 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 13 Oct 2021 16:08:37 +0300 Subject: [PATCH 082/104] WIP [skip ci] --- codegen/impl/src/es/event/mod.rs | 67 ++++++++----- core/src/es/adapter/mod.rs | 97 +++++++++++++++++++ .../es/adapter/transformer/strategy/as_is.rs | 4 +- .../es/adapter/transformer/strategy/custom.rs | 8 +- .../es/adapter/transformer/strategy/mod.rs | 19 +++- .../es/adapter/transformer/strategy/skip.rs | 4 +- .../es/adapter/transformer/strategy/split.rs | 4 +- 7 files changed, 172 insertions(+), 31 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index b68bf53..89c34c6 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -64,13 +64,25 @@ pub struct Definition { /// [`event::Sourced`]: arcana_core::es::event::Sourced /// [`Field`]: syn::Field /// [`Variant`]: syn::Variant - pub variants: Vec<(syn::Variant, bool)>, + pub variants: Vec, /// Indicator whether this enum has any variants marked with /// `#[event(ignore)]` attribute. pub has_ignored_variants: bool, } +/// Parsed single-field enum variant for [`Event`] derive macro. +/// +/// [`Event`]: arcana_core::es::Event +#[derive(Clone, Debug)] +pub struct SingleFieldVariant { + /// [`syn::Variant`] itself + variant: syn::Variant, + + /// Indicates, whether `#[event(init)]` attribute is present or not. + is_initial: bool, +} + impl TryFrom for Definition { type Error = syn::Error; @@ -120,7 +132,7 @@ impl Definition { /// and is not ignored. fn parse_variant( variant: &syn::Variant, - ) -> syn::Result> { + ) -> syn::Result> { let attrs = VariantAttrs::parse_attrs("event", variant)?; if let Some(init) = &attrs.init { @@ -150,7 +162,10 @@ impl Definition { )); } - Ok(Some((variant.clone(), attrs.init.is_some()))) + Ok(Some(SingleFieldVariant { + variant: variant.clone(), + is_initial: attrs.init.is_some(), + })) } /// Substitutes the given [`syn::Generics`] with trivial types. @@ -169,14 +184,6 @@ impl Definition { quote! { < #( #generics ),* > } } - /// Returns [`Iterator`] of enum variant [`syn::Type`]s. - #[must_use] - pub fn variant_types(&self) -> impl DoubleEndedIterator { - self.variants - .iter() - .filter_map(|var| var.0.fields.iter().next().map(|f| &f.ty)) - } - /// Generates code to derive [`Event`][0] trait, by simply matching over /// each enum variant, which is expected to be itself an [`Event`][0] /// implementer. @@ -187,7 +194,11 @@ impl Definition { let ty = &self.ident; let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - let var = self.variants.iter().map(|v| &v.0.ident).collect::>(); + let var = self + .variants + .iter() + .map(|v| &v.variant.ident) + .collect::>(); let unreachable_arm = self.has_ignored_variants.then(|| { quote! { _ => unreachable!(), } @@ -228,9 +239,9 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); let turbofish_gens = ty_gens.as_turbofish(); - let var_ty = self.variants.iter().map(|(v, is_initial)| { - let var_ty = v.fields.iter().next().map(|f| &f.ty); - if *is_initial { + let var_ty = self.variants.iter().map(|v| { + let var_ty = v.variant.fields.iter().next().map(|f| &f.ty); + if v.is_initial { quote! { ::arcana::es::event::Initial<#var_ty> } } else { quote! { #var_ty } @@ -244,11 +255,11 @@ impl Definition { }); let (impl_gens, _, where_clause) = ext_gens.split_for_impl(); - let arms = self.variants.iter().map(|(v, is_initial)| { - let var = &v.ident; - let var_ty = v.fields.iter().next().map(|f| &f.ty); + let arms = self.variants.iter().map(|v| { + let var = &v.variant.ident; + let var_ty = v.variant.fields.iter().next().map(|f| &f.ty); - let event = if *is_initial { + let event = if v.is_initial { quote! { <::arcana::es::event::Initial<#var_ty> as ::arcana::RefCast>::ref_cast(f) @@ -302,7 +313,7 @@ impl Definition { let var_ty = self .variants .iter() - .flat_map(|v| &v.0.fields) + .flat_map(|v| &v.variant.fields) .map(|f| &f.ty) .collect::>(); @@ -421,7 +432,11 @@ impl Definition { #[must_use] pub fn transformer_generics(&self) -> syn::Generics { let mut generics = self.generics.clone(); - let var_type = self.variant_types().collect::>(); + let var_type = self + .variants + .iter() + .filter_map(|var| var.variant.fields.iter().next().map(|f| &f.ty)) + .collect::>(); let additional_generic_params: Punctuated< syn::GenericParam, @@ -511,7 +526,9 @@ impl Definition { } }; - self.variant_types() + self.variants + .iter() + .filter_map(|var| var.variant.fields.iter().next().map(|f| &f.ty)) .rev() .fold(None, |acc, ty| { let variant_stream = transformed_stream(ty); @@ -552,7 +569,11 @@ impl Definition { self.variants .iter() .filter_map(|var| { - var.0.fields.iter().next().map(|f| (&var.0.ident, &f.ty)) + var.variant + .fields + .iter() + .next() + .map(|f| (&var.variant.ident, &f.ty)) }) .enumerate() .map(|(i, (variant_ident, var_ty))| { diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index decc905..62064e6 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -126,6 +126,103 @@ pub trait Returning { /// # } /// ``` /// +/// In case you want to use custom context, it should implement [`Borrow`] +/// `dyn `[`AnyContext`]. +/// +/// ```rust +/// # #![feature(generic_associated_types)] +/// # +/// # use std::{borrow::Borrow, convert::Infallible}; +/// # +/// # use arcana::es::{ +/// # adapter::{self, strategy::{self, AnyContext}}, +/// # Event, Adapter as _, VersionedEvent, +/// # }; +/// # use derive_more::From; +/// # use futures::{stream, TryStreamExt as _}; +/// # +/// # #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// # #[event(name = "chat", version = 1)] +/// # struct ChatEvent; +/// # +/// # #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// # #[event(name = "file", version = 2)] +/// # struct FileEvent; +/// # +/// # // Some outdated Event. +/// # #[derive(Clone, Copy, Debug, PartialEq, VersionedEvent)] +/// # #[event(name = "file", version = 1)] +/// # struct FileEventV1; +/// # +/// # // Repository-level Event, which is loaded from some Event Store and +/// # // includes legacy Events. +/// # #[derive(Clone, Copy, Debug, Event, PartialEq, From)] +/// # enum RepositoryEvent { +/// # FileV1(FileEventV1), +/// # File(FileEvent), +/// # Chat(ChatEvent), +/// # } +/// # +/// # // Actual Event we want to transform RepositoryEvent into +/// # #[derive(Clone, Copy, Debug, Event, From, PartialEq)] +/// # enum FileDomainEvent { +/// # File(FileEvent), +/// # } +/// # +/// # #[derive(Clone, Copy)] +/// # struct Adapter; +/// # +/// # impl adapter::Returning for Adapter { +/// # type Error = Infallible; +/// # type Transformed = FileDomainEvent; +/// # } +/// # +/// # impl adapter::Adapt for Adapter { +/// # type Strategy = strategy::AsIs; +/// # } +/// # +/// # impl adapter::Adapt for Adapter { +/// # type Strategy = strategy::Into; +/// # } +/// # +/// # impl adapter::Adapt for Adapter { +/// # type Strategy = strategy::Skip; +/// # } +/// # +/// # let assertion = async { +/// # let events = stream::iter::<[RepositoryEvent; 3]>([ +/// # FileEventV1.into(), +/// # FileEvent.into(), +/// # ChatEvent.into(), +/// # ]); +/// struct CustomContext; +/// +/// impl Borrow for CustomContext { +/// fn borrow(&self) -> &(dyn AnyContext + 'static) { +/// self +/// } +/// } +/// +/// let transformed = Adapter +/// .transform_all(events, &CustomContext) +/// .try_collect::>() +/// .await +/// .unwrap(); +/// +/// assert_eq!(transformed, vec![FileEvent.into(), FileEvent.into()]); +/// # }; +/// # +/// # futures::executor::block_on(assertion); +/// # +/// # impl From for FileEvent { +/// # fn from(_: FileEventV1) -> Self { +/// # Self +/// # } +/// # } +/// ``` +/// +/// [`AnyContext`]: transformer::strategy::AnyContext +/// [`Borrow`]: std::borrow::Borrow /// [`Error`]: Self::Error /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip diff --git a/core/src/es/adapter/transformer/strategy/as_is.rs b/core/src/es/adapter/transformer/strategy/as_is.rs index ece815c..3b550e1 100644 --- a/core/src/es/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/adapter/transformer/strategy/as_is.rs @@ -4,7 +4,7 @@ use futures::{future, stream}; use crate::es::{adapter, event}; -use super::Strategy; +use super::{AnyContext, Strategy}; /// [`Strategy`] for passing [`Event`]s as is, without any conversions. /// @@ -18,7 +18,7 @@ where Adapter::Error: 'static, Event: event::VersionedOrRaw + 'static, { - type Context = (); + type Context = dyn AnyContext; type Error = Adapter::Error; type Transformed = Event; type TransformedStream<'o> = diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index 4a4e83a..b9dee02 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -15,7 +15,13 @@ pub struct Custom; /// /// [`Transformed`]: Self::Transformed pub trait Customize { - /// TODO + /// Context of this [`Custom`] [`Strategy`]. + /// + /// In real world this is usually `dyn Trait`. In that case, + /// [`Adapter::transform_all()`][1] will expect concrete type which can be + /// [`Borrow`]ed as `dyn Trait`. + /// + /// [1]: adapter::Adapter type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index 3edf63a..c713ae9 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -27,7 +27,13 @@ pub use self::{ /// /// [`Versioned`]: event::Versioned pub trait Strategy { - /// TODO + /// Context of this [`Strategy`]. + /// + /// In real world this is usually `dyn Trait`. In that case, + /// [`Adapter::transform_all()`][1] will expect concrete type which can be + /// [`Borrow`]ed as `dyn Trait`. + /// + /// [1]: adapter::Adapter type Context: ?Sized; /// Error of this [`Strategy`]. @@ -98,3 +104,14 @@ where ) } } + +/// Context, that intended to be [`Borrow`]ed as `&dyn AnyContext`. +pub trait AnyContext {} + +impl AnyContext for T {} + +impl Borrow<(dyn AnyContext + 'static)> for () { + fn borrow(&self) -> &(dyn AnyContext + 'static) { + self + } +} diff --git a/core/src/es/adapter/transformer/strategy/skip.rs b/core/src/es/adapter/transformer/strategy/skip.rs index 40d64a6..357c83a 100644 --- a/core/src/es/adapter/transformer/strategy/skip.rs +++ b/core/src/es/adapter/transformer/strategy/skip.rs @@ -4,7 +4,7 @@ use futures::stream; use crate::es::{adapter, event}; -use super::Strategy; +use super::{AnyContext, Strategy}; /// [`Strategy`] for skipping [`Event`]s. /// @@ -19,7 +19,7 @@ where Adapter::Transformed: 'static, Adapter::Error: 'static, { - type Context = (); + type Context = dyn AnyContext; type Error = Adapter::Error; type Transformed = Adapter::Transformed; type TransformedStream<'o> = diff --git a/core/src/es/adapter/transformer/strategy/split.rs b/core/src/es/adapter/transformer/strategy/split.rs index 42aae57..1ecb056 100644 --- a/core/src/es/adapter/transformer/strategy/split.rs +++ b/core/src/es/adapter/transformer/strategy/split.rs @@ -6,7 +6,7 @@ use futures::{stream, StreamExt as _}; use crate::es::{adapter, event}; -use super::Strategy; +use super::{AnyContext, Strategy}; /// [`Strategy`] for splitting single [`Event`] into multiple. Implement /// [`Splitter`] to define splitting logic. @@ -38,7 +38,7 @@ where Event: event::VersionedOrRaw, IntoEvent: 'static, { - type Context = (); + type Context = dyn AnyContext; type Error = Adapter::Error; type Transformed = ::Item; type TransformedStream<'o> = SplitStream; From 9cc95bc938b5398e8862b5130cac49c5da2c307a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 14 Oct 2021 11:20:30 +0300 Subject: [PATCH 083/104] Corrections --- codegen/impl/src/es/event/mod.rs | 18 ++++++++++-------- codegen/shim/src/lib.rs | 3 ++- core/src/es/adapter/mod.rs | 9 ++++++--- core/src/es/adapter/transformer/mod.rs | 2 +- .../es/adapter/transformer/strategy/custom.rs | 3 ++- .../src/es/adapter/transformer/strategy/mod.rs | 6 +++--- core/src/es/event.rs | 5 +++-- core/src/lib.rs | 2 -- examples/chat/src/storage/message.rs | 18 ++++-------------- src/es/adapter/mod.rs | 7 +++++-- src/es/adapter/transformer/strategy.rs | 2 +- src/lib.rs | 2 -- 12 files changed, 37 insertions(+), 40 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 89c34c6..3a42e44 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -71,12 +71,10 @@ pub struct Definition { pub has_ignored_variants: bool, } -/// Parsed single-field enum variant for [`Event`] derive macro. -/// -/// [`Event`]: arcana_core::es::Event +/// Parsed single-field enum variant for `#[derive(Event)]` macro. #[derive(Clone, Debug)] pub struct SingleFieldVariant { - /// [`syn::Variant`] itself + /// [`syn::Variant`] itself. variant: syn::Variant, /// Indicates, whether `#[event(init)]` attribute is present or not. @@ -262,7 +260,8 @@ impl Definition { let event = if v.is_initial { quote! { <::arcana::es::event::Initial<#var_ty> - as ::arcana::RefCast>::ref_cast(f) + as ::arcana::es::event::codegen::ref_cast::RefCast + >::ref_cast(f) } } else { quote! { f } @@ -672,8 +671,9 @@ mod spec { Event::File(f) => { ::arcana::es::event::Sourced::apply( self, - <::arcana::es::event::Initial - as ::arcana::RefCast>::ref_cast(f) + <::arcana::es::event::Initial as + ::arcana::es::event::codegen::ref_cast::RefCast + >::ref_cast(f) ); }, Event::Chat(f) => { @@ -952,7 +952,9 @@ mod spec { ::arcana::es::event::Sourced::apply( self, <::arcana::es::event::Initial > - as ::arcana::RefCast>::ref_cast(f) + as ::arcana::es::event::codegen::ref_cast:: + RefCast + >::ref_cast(f) ); }, Event::<'a, F, C>::Chat(f) => { diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 5fe38fb..15a0eca 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -44,7 +44,8 @@ use proc_macro::TokenStream; /// /// # Blanket implementations /// -/// - [`Sourced`] for every state, which can be sourced from all enum variants; +/// - [`event::Sourced`] for every state, which can be sourced from all enum +/// variants; /// - [`Transformer`] for every [`Adapter`], that can transform all enum /// variants. /// diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 62064e6..0d707b5 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -13,7 +13,10 @@ use pin_project::pin_project; use ref_cast::RefCast; #[doc(inline)] -pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; +pub use self::transformer::{ + strategy::{self, AnyContext}, + Adapt, Strategy, Transformer, +}; /// Specifies result of [`Adapter`]. pub trait Returning { @@ -35,7 +38,7 @@ pub trait Returning { /// /// Usually provided as blanket impl, so you shouldn't implement it manually. /// For that you'll need to implement [`Returning`] to specify transformation -/// result and [`WithStrategy`] for every [`VersionedEvent`] which is part of +/// result and [`Adapt`] for every [`VersionedEvent`] which is part of /// transformed [`Event`]. And as long as [`Event`] is implemented via derive /// macro you should be good to go. /// @@ -127,7 +130,7 @@ pub trait Returning { /// ``` /// /// In case you want to use custom context, it should implement [`Borrow`] -/// `dyn `[`AnyContext`]. +/// `dyn `[`AnyContext`] and all other used [`Strategy::Context`]s. /// /// ```rust /// # #![feature(generic_associated_types)] diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/adapter/transformer/mod.rs index 7a4262b..aa1aa1d 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/adapter/transformer/mod.rs @@ -29,7 +29,7 @@ pub trait Adapt { /// - Transforming (ex: from one [`Version`] to another); /// - [`Split`]ting existing [`Event`]s into more granular ones. /// -/// Provided with blanket impl for [`WithStrategy`] implementors, so usually you +/// Provided with blanket impl for [`Adapt`] implementors, so usually you /// shouldn't implement it manually. For more flexibility consider using /// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. /// See [`Adapter`] for more info. diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/adapter/transformer/strategy/custom.rs index b9dee02..87d262a 100644 --- a/core/src/es/adapter/transformer/strategy/custom.rs +++ b/core/src/es/adapter/transformer/strategy/custom.rs @@ -21,7 +21,8 @@ pub trait Customize { /// [`Adapter::transform_all()`][1] will expect concrete type which can be /// [`Borrow`]ed as `dyn Trait`. /// - /// [1]: adapter::Adapter + /// [1]: crate::es::Adapter + /// [`Borrow`]: std::borrow::Borrow type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index c713ae9..7b7dcb5 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -30,7 +30,7 @@ pub trait Strategy { /// Context of this [`Strategy`]. /// /// In real world this is usually `dyn Trait`. In that case, - /// [`Adapter::transform_all()`][1] will expect concrete type which can be + /// [`Adapter::transform_all()`][1] will expect type which can be /// [`Borrow`]ed as `dyn Trait`. /// /// [1]: adapter::Adapter @@ -105,10 +105,10 @@ where } } -/// Context, that intended to be [`Borrow`]ed as `&dyn AnyContext`. +/// [`Strategy::Context`] implemented for every type. pub trait AnyContext {} -impl AnyContext for T {} +impl AnyContext for T {} impl Borrow<(dyn AnyContext + 'static)> for () { fn borrow(&self) -> &(dyn AnyContext + 'static) { diff --git a/core/src/es/event.rs b/core/src/es/event.rs index 63fd9a3..5fe3a20 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event.rs @@ -214,8 +214,8 @@ impl> Sourced> } } -// TODO: Maybe replace `Ev` with `const Name` (same as `const &'static str`), -// once `adt_const_params` feature stabilizes. +// TODO: Replace `Ev` with `const Name` (same as `const &'static str`), once +// `adt_const_params` feature stabilizes. // https://github.com/rust-lang/rust/issues/44580 /// Raw [`Versioned`] event. /// @@ -300,6 +300,7 @@ pub mod codegen { use super::Raw; pub use futures; + pub use ref_cast; impl Raw { #[doc(hidden)] diff --git a/core/src/lib.rs b/core/src/lib.rs index 6977b1d..6f36a58 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,5 +24,3 @@ #[cfg(feature = "es")] pub mod es; - -pub use ref_cast::RefCast; diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 4f710e0..a685243 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,6 +1,6 @@ -use std::{borrow::Borrow, convert::Infallible}; +use std::convert::Infallible; -use arcana::es::adapter::{self, strategy, Adapt}; +use arcana::es::adapter::{self, strategy, Adapt, AnyContext}; use futures::stream; use crate::event; @@ -47,9 +47,9 @@ impl Adapt> type Strategy = strategy::Skip; } -// Basically same as Skip, but with additional Ctx bounds +// Basically same as Skip impl strategy::Customize for Adapter { - type Context = dyn Bound; + type Context = dyn AnyContext; type Error = Infallible; type Transformed = event::Message; type TransformedStream<'out> = @@ -67,13 +67,3 @@ impl strategy::Customize for Adapter { stream::empty() } } - -pub trait Bound {} - -impl Bound for () {} - -impl Borrow<(dyn Bound + 'static)> for () { - fn borrow(&self) -> &(dyn Bound + 'static) { - self - } -} diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs index 07c1572..b0585d3 100644 --- a/src/es/adapter/mod.rs +++ b/src/es/adapter/mod.rs @@ -3,9 +3,12 @@ pub mod transformer; #[doc(inline)] -pub use self::transformer::Transformer; +pub use self::transformer::{ + strategy::{self, AnyContext}, + Adapt, Strategy, Transformer, +}; #[doc(inline)] pub use arcana_core::es::adapter::{ - strategy, Adapt, Adapter, Returning, Strategy, TransformedStream, Wrapper, + Adapter, Returning, TransformedStream, Wrapper, }; diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs index 7fdad58..aa0d3e7 100644 --- a/src/es/adapter/transformer/strategy.rs +++ b/src/es/adapter/transformer/strategy.rs @@ -2,5 +2,5 @@ #[doc(inline)] pub use arcana_core::es::adapter::transformer::strategy::{ - AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, + AnyContext, AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, }; diff --git a/src/lib.rs b/src/lib.rs index 22972b9..cca5c8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,5 +23,3 @@ #[cfg(feature = "es")] pub mod es; - -pub use arcana_core::RefCast; From bda309f8466208c18bd1cb25b3eb126221996027 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 14 Oct 2021 11:46:06 +0300 Subject: [PATCH 084/104] Corrections --- core/Cargo.toml | 2 +- core/src/es/adapter/mod.rs | 4 +++- .../src/es/adapter/transformer/strategy/mod.rs | 2 +- examples/chat/src/storage/email.rs | 18 +++++------------- examples/chat/src/storage/message.rs | 18 ++++++++++++++---- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 7114f7c..e8944ff 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,7 +26,7 @@ sealed = { version = "0.3", optional = true } [dev-dependencies] arcana = { version = "0.1.0-dev", path = "..", features = ["derive", "es"] } -futures = { version = "0.3", features = ["executor"] } +futures = "0.3" [package.metadata.docs.rs] all-features = true diff --git a/core/src/es/adapter/mod.rs b/core/src/es/adapter/mod.rs index 0d707b5..dfd8853 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/adapter/mod.rs @@ -130,7 +130,8 @@ pub trait Returning { /// ``` /// /// In case you want to use custom context, it should implement [`Borrow`] -/// `dyn `[`AnyContext`] and all other used [`Strategy::Context`]s. +/// `dyn `[`Strategy::Context`]s for all used [`Strategies`]. Default ones use +/// [`AnyContext`]. /// /// ```rust /// # #![feature(generic_associated_types)] @@ -230,6 +231,7 @@ pub trait Returning { /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split +/// [`Strategies`]: Strategy /// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version /// [`VersionedEvent`]: crate::es::VersionedEvent diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/adapter/transformer/strategy/mod.rs index 7b7dcb5..920cbb7 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/adapter/transformer/strategy/mod.rs @@ -33,7 +33,7 @@ pub trait Strategy { /// [`Adapter::transform_all()`][1] will expect type which can be /// [`Borrow`]ed as `dyn Trait`. /// - /// [1]: adapter::Adapter + /// [1]: adapter::Adapter::transform_all type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 06a5fbf..b85a604 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,6 +1,8 @@ -use std::{array, borrow::Borrow, iter}; +use std::{array, iter}; -use arcana::es::adapter::{self, strategy, strategy::Splitter, Adapt}; +use arcana::es::adapter::{ + self, strategy, strategy::Splitter, Adapt, AnyContext, +}; use either::Either; use futures::{future, stream, StreamExt as _}; @@ -87,7 +89,7 @@ impl event::Raw, > for Adapter { - type Context = dyn Bound; + type Context = dyn AnyContext; type Error = serde_json::Error; type Transformed = Either; type TransformedStream<'out> = CustomizedStream; @@ -148,13 +150,3 @@ impl From> } } } - -pub trait Bound {} - -impl Bound for () {} - -impl Borrow<(dyn Bound + 'static)> for () { - fn borrow(&self) -> &(dyn Bound + 'static) { - self - } -} diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index a685243..be94bb4 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,6 +1,6 @@ -use std::convert::Infallible; +use std::{borrow::Borrow, convert::Infallible}; -use arcana::es::adapter::{self, strategy, Adapt, AnyContext}; +use arcana::es::adapter::{self, strategy, Adapt}; use futures::stream; use crate::event; @@ -47,9 +47,9 @@ impl Adapt> type Strategy = strategy::Skip; } -// Basically same as Skip +// Basically same as Skip, but with additional Context bounds. impl strategy::Customize for Adapter { - type Context = dyn AnyContext; + type Context = dyn Bound; type Error = Infallible; type Transformed = event::Message; type TransformedStream<'out> = @@ -67,3 +67,13 @@ impl strategy::Customize for Adapter { stream::empty() } } + +pub trait Bound {} + +impl Bound for () {} + +impl Borrow<(dyn Bound + 'static)> for () { + fn borrow(&self) -> &(dyn Bound + 'static) { + self + } +} From 8e5486da72e4cd2d1d990d3b5f9c6df3b7157ade Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 14 Oct 2021 12:04:57 +0300 Subject: [PATCH 085/104] CHANGELOG --- CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c5363..d8d4b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,18 @@ All user visible changes to this project will be documented in this file. This p - Proc macros - `Event` derive - `VersionedEvent` derive - + - Transforming Events + - Traits + - `Adapter` + - `Transformer` + - `Adapt` + - `Strategy` + - Structs + - `strategy::AsIs` + - `strategy::Custom` + - `strategy::Into` + - `strategy::Skip` + - `strategy::Split` From 18d18ecbf0e9230e44330834e9a94053bffd6437 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 14 Oct 2021 17:42:29 +0300 Subject: [PATCH 086/104] Some corrections [skip ci] --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 6 +++++- core/Cargo.toml | 2 +- core/src/es/adapter/transformer/strategy/into.rs | 4 +--- examples/chat/Cargo.toml | 8 ++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10ca460..c7af89e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: - arcana-codegen-shim - arcana-codegen - arcana - - arcana-chat-example + - arcana-example-chat os: - ubuntu - macOS @@ -135,7 +135,7 @@ jobs: - arcana-codegen-shim - arcana-codegen - arcana - - arcana-chat-example + - arcana-example-chat runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index fd1d6c5..4e94784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [workspace] -members = ["codegen", "codegen/impl", "codegen/shim", "core", "examples/chat"] +members = [ + "codegen", "codegen/impl", "codegen/shim", + "core", + "examples/chat", +] diff --git a/core/Cargo.toml b/core/Cargo.toml index e8944ff..27195cf 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,7 +19,7 @@ es = [] [dependencies] derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "from", "into"], default-features = false } -futures = { version = "0.3", default-features = false } +futures = { version = "0.3.11", default-features = false } pin-project = "1.0" ref-cast = "1.0" sealed = { version = "0.3", optional = true } diff --git a/core/src/es/adapter/transformer/strategy/into.rs b/core/src/es/adapter/transformer/strategy/into.rs index 54ec383..a406779 100644 --- a/core/src/es/adapter/transformer/strategy/into.rs +++ b/core/src/es/adapter/transformer/strategy/into.rs @@ -28,7 +28,7 @@ where type Transformed = IntoEvent; type TransformedStream<'out> = stream::MapOk< InnerStrategy::TransformedStream<'out>, - IntoFn, + fn(InnerStrategy::Transformed) -> IntoEvent, >; fn transform<'me: 'out, 'ctx: 'out, 'out>( @@ -39,5 +39,3 @@ where InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } } - -type IntoFn = fn(FromEvent) -> IntoEvent; diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index 3522b39..58784d9 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "arcana-chat-example" -version = "0.1.0-dev" +name = "arcana-example-chat" +version = "0.0.0" edition = "2018" resolver = "2" description = "Simple chat app using arcana framework." @@ -13,8 +13,8 @@ publish = false [dependencies] arcana = { version = "0.1.0-dev", path = "../..", features = ["derive", "es"] } derive_more = "0.99" -either = "1" +either = "1.0" futures = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1.0", features = ["full"] } From 7512e06ab6b03269ec57f48e2ac93f025409cf00 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 19 Oct 2021 12:10:53 +0300 Subject: [PATCH 087/104] Add event::Adapter derive macro [skip ci] --- codegen/impl/src/es/event/adapter.rs | 185 ++++++++++ codegen/impl/src/es/event/mod.rs | 317 ++++++++++-------- codegen/shim/src/lib.rs | 43 ++- codegen/src/es/event.rs | 4 +- core/src/es/{ => event}/adapter/mod.rs | 24 +- .../es/{ => event}/adapter/transformer/mod.rs | 4 +- .../adapter/transformer/strategy/as_is.rs | 2 +- .../adapter/transformer/strategy/into.rs | 4 +- .../adapter/transformer/strategy/mod.rs | 2 +- .../adapter/transformer/strategy/skip.rs | 2 +- .../adapter/transformer/strategy/split.rs | 2 +- core/src/es/{event.rs => event/mod.rs} | 9 +- core/src/es/mod.rs | 11 +- examples/chat/src/storage/chat.rs | 15 +- examples/chat/src/storage/email.rs | 10 +- examples/chat/src/storage/message.rs | 13 +- examples/chat/src/storage/mod.rs | 6 +- src/es/adapter/mod.rs | 14 - src/es/adapter/transformer/strategy.rs | 6 - src/es/event/adapter/mod.rs | 15 + src/es/{ => event}/adapter/transformer/mod.rs | 2 +- src/es/event/adapter/transformer/strategy.rs | 7 + src/es/{event.rs => event/mod.rs} | 8 +- src/es/mod.rs | 11 +- 24 files changed, 488 insertions(+), 228 deletions(-) create mode 100644 codegen/impl/src/es/event/adapter.rs rename core/src/es/{ => event}/adapter/mod.rs (95%) rename core/src/es/{ => event}/adapter/transformer/mod.rs (96%) rename core/src/es/{ => event}/adapter/transformer/strategy/as_is.rs (95%) rename core/src/es/{ => event}/adapter/transformer/strategy/into.rs (90%) rename core/src/es/{ => event}/adapter/transformer/strategy/mod.rs (98%) rename core/src/es/{ => event}/adapter/transformer/strategy/skip.rs (95%) rename core/src/es/{ => event}/adapter/transformer/strategy/split.rs (97%) rename core/src/es/{event.rs => event/mod.rs} (98%) delete mode 100644 src/es/adapter/mod.rs delete mode 100644 src/es/adapter/transformer/strategy.rs create mode 100644 src/es/event/adapter/mod.rs rename src/es/{ => event}/adapter/transformer/mod.rs (60%) create mode 100644 src/es/event/adapter/transformer/strategy.rs rename src/es/{event.rs => event/mod.rs} (73%) diff --git a/codegen/impl/src/es/event/adapter.rs b/codegen/impl/src/es/event/adapter.rs new file mode 100644 index 0000000..e888879 --- /dev/null +++ b/codegen/impl/src/es/event/adapter.rs @@ -0,0 +1,185 @@ +//! `#[derive(event::Adapter)]` macro implementation. + +use std::convert::TryFrom; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse_quote; +use synthez::{ParseAttrs, Required, ToTokens}; + +/// Expands `#[derive(event::Adapter)]` macro. +/// +/// # Errors +/// +/// If failed to parse [`Attrs`]. +pub fn derive(input: TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + let definition = Definition::try_from(input)?; + + Ok(quote! { #definition }) +} + +/// Helper attributes of `#[derive(event::Adapter)]` macro. +#[derive(Debug, Default, ParseAttrs)] +pub struct Attrs { + /// [`Returning::Transformed`][1] associated type. + /// + /// [1]: arcana_core::es::event::adapter::Returning::Transformed + #[parse(value, alias = into)] + pub transformed: Required, + + /// [`Returning::Error`][1] associated type. + /// + /// [1]: arcana_core::es::event::adapter::Returning::Error + #[parse(value, alias = err)] + pub error: Option, +} + +/// Representation of a struct implementing [`event::Adapter`][0], used for +/// code generation. +/// +/// [0]: arcana_core::es::event::Adapter +#[derive(Debug, ToTokens)] +#[to_tokens(append(impl_returning))] +pub struct Definition { + /// [`syn::Ident`](struct@syn::Ident) of this type. + pub ident: syn::Ident, + + /// [`syn::Generics`] of this type. + pub generics: syn::Generics, + + /// [`Returning::Transformed`][1] associated type. + /// + /// [1]: arcana_core::es::event::adapter::Returning::Transformed + pub transformed: syn::Type, + + /// [`Returning::Error`][1] associated type. + /// + /// [1]: arcana_core::es::event::adapter::Returning::Error + pub error: syn::Type, +} + +impl TryFrom for Definition { + type Error = syn::Error; + + fn try_from(input: syn::DeriveInput) -> syn::Result { + let attrs: Attrs = Attrs::parse_attrs("adapter", &input)?; + + Ok(Self { + ident: input.ident, + generics: input.generics, + transformed: attrs.transformed.into_inner(), + error: attrs + .error + .unwrap_or_else(|| parse_quote!(::std::convert::Infallible)), + }) + } +} + +impl Definition { + /// Generates code to derive [`Returning`][1] trait. + /// + /// [1]: arcana_core::es::event::adapter::Returning + #[must_use] + pub fn impl_returning(&self) -> TokenStream { + let ty = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + let (transformed, error) = (&self.transformed, &self.error); + + quote! { + #[automatically_derived] + impl #impl_gens ::arcana::es::event::adapter::Returning for + #ty#ty_gens + #where_clause + { + type Error = #error; + type Transformed = #transformed; + } + } + } +} + +#[cfg(test)] +mod spec { + use quote::quote; + use syn::parse_quote; + + #[test] + fn derives_impl() { + let input = parse_quote! { + #[adapter(into = Event, error = CustomError)] + struct Adapter; + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::es::event::adapter::Returning for Adapter { + type Error = CustomError; + type Transformed = Event; + } + }; + + assert_eq!( + super::derive(input).unwrap().to_string(), + output.to_string(), + ); + } + + #[test] + fn derives_impl_with_default_infallible_error() { + let input = parse_quote! { + #[adapter(into = Event)] + struct Adapter; + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::es::event::adapter::Returning for Adapter { + type Error = ::std::convert::Infallible; + type Transformed = Event; + } + }; + + assert_eq!( + super::derive(input).unwrap().to_string(), + output.to_string(), + ); + } + + #[test] + fn derives_impl_with_generics() { + let input = parse_quote! { + #[adapter(transformed = Event, err = CustomError)] + struct Adapter(T); + }; + + let output = quote! { + #[automatically_derived] + impl ::arcana::es::event::adapter::Returning for Adapter { + type Error = CustomError; + type Transformed = Event; + } + }; + + assert_eq!( + super::derive(input).unwrap().to_string(), + output.to_string(), + ); + } + + #[test] + fn transformed_arg_is_required() { + let input = parse_quote! { + #[adapter(error = CustomError)] + struct Adapter; + }; + + let err = super::derive(input).unwrap_err(); + + assert_eq!( + err.to_string(), + "either `into` or `transformed` argument of `#[adapter]` attribute \ + is expected to be present, but is absent", + ); + } +} diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 3a42e44..57b2361 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -1,5 +1,6 @@ //! `#[derive(Event)]` macro implementation. +pub mod adapter; pub mod versioned; use std::{convert::TryFrom, iter}; @@ -372,9 +373,9 @@ impl Definition { /// Generates code to derive [`Transformer`][0] trait for any [wrapped][1] /// [`Adapter`][2], which can transform every enum variant. /// - /// [0]: arcana_core::es::adapter::Transformer - /// [1]: arcana_core::es::adapter::Wrapper - /// [2]: arcana_core::es::Adapter + /// [0]: arcana_core::es::event::adapter::Transformer + /// [1]: arcana_core::es::event::adapter::Wrapper + /// [2]: arcana_core::es::event::Adapter #[must_use] pub fn impl_transformer(&self) -> TokenStream { let event = &self.ident; @@ -392,21 +393,22 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gen ::arcana::es::adapter::Transformer< + impl#impl_gen ::arcana::es::event::adapter::Transformer< '__ctx, #event#type_gen, __Ctx - > for ::arcana::es::adapter::Wrapper<__A> #where_clause + > for ::arcana::es::event::adapter::Wrapper<__A> #where_clause { - type Error = <__A as ::arcana::es::adapter::Returning>:: + type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::Returning>:: - Transformed; + type Transformed = + <__A as ::arcana::es::event::adapter::Returning>:: + Transformed; type TransformedStream<'out> = #transformed; fn transform<'me, 'out>( &'me self, __event: #event#type_gen, __context: &'__ctx __Ctx, - ) -> >:: TransformedStream<'out> where @@ -425,9 +427,9 @@ impl Definition { /// Generates [`syn::Generics`] to for [wrapped][0] [`Adapter`][1], which /// [`transform`][2]s every enum variant. /// - /// [0]: arcana_core::es::adapter::Wrapper - /// [1]: arcana_core::es::Adapter - /// [2]: arcana_core::es::adapter::Transformer::transform + /// [0]: arcana_core::es::event::adapter::Wrapper + /// [1]: arcana_core::es::event::Adapter + /// [2]: arcana_core::es::event::adapter::Transformer::transform #[must_use] pub fn transformer_generics(&self) -> syn::Generics { let mut generics = self.generics.clone(); @@ -447,27 +449,35 @@ impl Definition { syn::WherePredicate, syn::Token![,], > = parse_quote! { - __A: ::arcana::es::adapter::Returning, + __A: ::arcana::es::event::adapter::Returning, Self: #( - ::arcana::es::adapter::Transformer<'__ctx, #var_type, __Ctx> + ::arcana::es::event::adapter::Transformer< + '__ctx, #var_type, __Ctx + > )+*, - <__A as ::arcana::es::adapter::Returning>::Transformed: + <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static #( - + ::std::convert::From<>::Transformed> + + ::std::convert::From<< + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, #var_type, __Ctx + > + >::Transformed> )*, - <__A as ::arcana::es::adapter::Returning>::Error: + <__A as ::arcana::es::event::adapter::Returning>::Error: 'static #( - + ::std::convert::From<>::Error> + + ::std::convert::From<< + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, #var_type, __Ctx + > + >::Error> )*, #( - >::Transformed: 'static, - >::Error: 'static, )* @@ -490,8 +500,8 @@ impl Definition { /// [`stream::Map`] with a function that uses [`From`] impl to transform /// [`Event`]s into compatible ones. /// - /// [0]: arcana_core::es::adapter::Transformer::Transformed - /// [1]: arcana_core::es::adapter::Transformer::TransformedStream + /// [0]: arcana_core::es::event::adapter::Transformer::Transformed + /// [1]: arcana_core::es::event::adapter::Transformer::TransformedStream /// [`Either`]: futures::future::Either /// [`Event`]: trait@::arcana_core::es::Event /// [`stream::Map`]: futures::stream::Map @@ -503,22 +513,22 @@ impl Definition { let transformed_stream = |from: &syn::Type| { quote! { ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< - >:: Transformed, - >::Error, >, ) -> ::std::result::Result< - >:: Transformed, - >::Error, > > @@ -555,7 +565,7 @@ impl Definition { /// [`StreamExt::left_stream()`] and [`StreamExt::right_stream()`] /// combinators. /// - /// [0]: arcana_core::es::adapter::Transformer::transform + /// [0]: arcana_core::es::event::adapter::Transformer::transform /// [`Event`]: trait@arcana_core::es::Event /// [`StreamExt::left_stream()`]: futures::StreamExt::left_stream() /// [`StreamExt::right_stream()`]: futures::StreamExt::right_stream() @@ -578,7 +588,7 @@ impl Definition { .map(|(i, (variant_ident, var_ty))| { let stream_map = quote! { ::arcana::es::event::codegen::futures::StreamExt::map( - >::transform(self, __event, __context), { @@ -684,93 +694,106 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::adapter::Transformer< + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< '__ctx, Event, __Ctx - > for ::arcana::es::adapter::Wrapper<__A> + > for ::arcana::es::event::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::Returning, + __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, - <__A as ::arcana::es::adapter::Returning>::Transformed: + <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + - ::std::convert::From< >::Transformed> + - ::std::convert::From< >::Transformed>, - <__A as ::arcana::es::adapter::Returning>::Error: + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + >::Transformed> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + > + >::Transformed>, + <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + - ::std::convert::From< >::Error> + - ::std::convert::From< >::Error>, - + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + > + >::Error>, + >::Transformed: 'static, - >::Error: 'static, - >::Transformed: 'static, - >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::Returning>:: + type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::Returning>:: - Transformed; + type Transformed = + <__A as ::arcana::es::event::adapter::Returning>:: + Transformed; type TransformedStream<'out> = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< - >::Transformed, - >::Error, >, ) -> ::std::result::Result< - >:: Transformed, - >:: Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< - >::Transformed, - >::Error, >, ) -> ::std::result::Result< - >:: Transformed, - >:: Error, > @@ -781,7 +804,7 @@ mod spec { &'me self, __event: Event, __context: &'__ctx __Ctx, - ) -> >:: TransformedStream<'out> where @@ -794,7 +817,7 @@ mod spec { left_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - @@ -820,7 +843,7 @@ mod spec { right_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - @@ -965,100 +988,107 @@ mod spec { } #[automatically_derived] - impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::adapter:: + impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: Transformer<'__ctx, Event<'a, F, C>, __Ctx> for - ::arcana::es::adapter::Wrapper<__A> + ::arcana::es::event::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::Returning, + __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent<'a, F>, __Ctx > + - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent<'a, C>, __Ctx >, - <__A as ::arcana::es::adapter::Returning>::Transformed: + <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + - ::std::convert::From< , __Ctx> >::Transformed> + - ::std::convert::From< , __Ctx> >::Transformed>, - <__A as ::arcana::es::adapter::Returning>::Error: + <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + - ::std::convert::From< , __Ctx> >::Error> + - ::std::convert::From< , __Ctx> >::Error>, - , __Ctx + > + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent<'a, C>, __Ctx + > + >::Error>, + , __Ctx> >::Transformed: 'static, - , __Ctx> >::Error: 'static, - , __Ctx> >::Transformed: 'static, - , __Ctx> >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::Returning>:: + type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::Returning>:: - Transformed; + type Transformed = + <__A as ::arcana::es::event::adapter::Returning>:: + Transformed; type TransformedStream<'out> = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx >>::TransformedStream<'out>, fn( ::std::result::Result< - , __Ctx >>::Transformed, - , __Ctx >>::Error, >, ) -> ::std::result::Result< - , __Ctx >>::Transformed, - , __Ctx >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx >>::TransformedStream<'out>, fn( ::std::result::Result< - , __Ctx >>::Transformed, - , __Ctx >>::Error, >, ) -> ::std::result::Result< - , __Ctx >>::Transformed, - , __Ctx >>::Error, @@ -1070,7 +1100,7 @@ mod spec { &'me self, __event: Event<'a, F, C>, __context: &'__ctx __Ctx, - ) -> , __Ctx>>:: TransformedStream<'out> where @@ -1083,7 +1113,8 @@ mod spec { left_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - , __Ctx > >::transform(self, __event, __context), { @@ -1107,7 +1138,8 @@ mod spec { right_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - , __Ctx > >::transform(self, __event, __context), { @@ -1255,91 +1287,104 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::adapter::Transformer< + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< '__ctx, Event, __Ctx - > for ::arcana::es::adapter::Wrapper<__A> + > for ::arcana::es::event::adapter::Wrapper<__A> where - __A: ::arcana::es::adapter::Returning, + __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + - ::arcana::es::adapter::Transformer< + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, - <__A as ::arcana::es::adapter::Returning>::Transformed: + <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + - ::std::convert::From< >::Transformed> + - ::std::convert::From< >::Transformed>, - <__A as ::arcana::es::adapter::Returning>::Error: + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + >::Transformed> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + > + >::Transformed>, + <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + - ::std::convert::From< >::Error> + - ::std::convert::From< >::Error>, - + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + > + >::Error>, + >::Transformed: 'static, - >::Error: 'static, - >::Transformed: 'static, - >::Error: 'static { - type Error = <__A as ::arcana::es::adapter::Returning>:: + type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; - type Transformed = <__A as ::arcana::es::adapter::Returning>:: - Transformed; + type Transformed = + <__A as ::arcana::es::event::adapter::Returning>:: + Transformed; type TransformedStream<'out> = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< - >::Transformed, - >::Error, >, ) -> ::std::result::Result< - >:: Transformed, - >:: Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< - >::Transformed, - >::Error, >, ) -> ::std::result::Result< - >:: Transformed, - >:: Error, > @@ -1350,7 +1395,7 @@ mod spec { &'me self, __event: Event, __context: &'__ctx __Ctx, - ) -> >:: TransformedStream<'out> where @@ -1363,7 +1408,7 @@ mod spec { left_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - @@ -1389,7 +1434,7 @@ mod spec { right_stream( ::arcana::es::event::codegen::futures:: StreamExt::map( - diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index 15a0eca..872a568 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -122,11 +122,11 @@ use proc_macro::TokenStream; /// } /// ``` /// -/// [`Adapter`]: arcana_core::es::Adapter +/// [`Adapter`]: arcana_core::es::event::Adapter /// [`Event`]: arcana_core::es::Event /// [`event::Initialized`]: arcana_core::es::event::Initialized /// [`event::Sourced`]: arcana_core::es::event::Sourced -/// [`Transformer`]: arcana_core::es::adapter::Transformer +/// [`Transformer`]: arcana_core::es::event::adapter::Transformer /// [`Versioned`]: arcana_core::es::event::Versioned /// [0]: arcana_core::es::Event::name() /// [1]: arcana_core::es::Event::version() @@ -174,3 +174,42 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +/// Macro for deriving [`adapter::Returning`][0] which is required for +/// [`Adapter`] blanket impl. +/// +/// # Attributes +/// +/// #### `#[adapter(transformed = )]` +/// +/// Aliases: `#[adapter(into = )]` +/// +/// [`adapter::Returning::Transformed`][1] associated type. +/// +/// #### `#[adapter(error = )]` (optional) +/// +/// Aliases: `#[adapter(err = )]` +/// +/// [`adapter::Returning::Error`][2] associated type. [`Infallible`] by default. +/// +/// # Example +/// +/// ```rust +/// # use arcana::es::event; +/// # +/// #[derive(event::Versioned)] +/// #[event(name = "event", version = 1)] +/// struct Event; +/// ``` +/// +/// [`Adapter`]: arcana_core::es::event::Adapter +/// [`Infallible`]: std::convert::Infallible +/// [0]: arcana_core::es::event::adapter::Returning +/// [1]: arcana_core::es::event::adapter::Returning::Transformed +/// [2]: arcana_core::es::event::adapter::Returning::Error +#[proc_macro_derive(EventAdapter, attributes(adapter))] +pub fn derive_event_adapter(input: TokenStream) -> TokenStream { + codegen::es::event::adapter::derive(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/codegen/src/es/event.rs b/codegen/src/es/event.rs index 24c44d7..c2cb891 100644 --- a/codegen/src/es/event.rs +++ b/codegen/src/es/event.rs @@ -3,4 +3,6 @@ //! [`Event`]: arcana_core::es::Event #[doc(inline)] -pub use arcana_codegen_shim::{Event, VersionedEvent as Versioned}; +pub use arcana_codegen_shim::{ + Event, EventAdapter as Adapter, VersionedEvent as Versioned, +}; diff --git a/core/src/es/adapter/mod.rs b/core/src/es/event/adapter/mod.rs similarity index 95% rename from core/src/es/adapter/mod.rs rename to core/src/es/event/adapter/mod.rs index dfd8853..0f3b091 100644 --- a/core/src/es/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -50,8 +50,8 @@ pub trait Returning { /// # use std::convert::Infallible; /// # /// # use arcana::es::{ -/// # adapter::{self, strategy}, -/// # Event, Adapter as _, VersionedEvent, +/// # event::adapter::{self, strategy}, +/// # Event, EventAdapter, VersionedEvent, /// # }; /// # use derive_more::From; /// # use futures::{stream, TryStreamExt as _}; @@ -84,14 +84,10 @@ pub trait Returning { /// File(FileEvent), /// } /// -/// #[derive(Clone, Copy)] +/// #[derive(Clone, Copy, EventAdapter)] +/// #[adapter(into = FileDomainEvent)] /// struct Adapter; /// -/// impl adapter::Returning for Adapter { -/// type Error = Infallible; -/// type Transformed = FileDomainEvent; -/// } -/// /// impl adapter::Adapt for Adapter { /// type Strategy = strategy::AsIs; /// } @@ -139,8 +135,8 @@ pub trait Returning { /// # use std::{borrow::Borrow, convert::Infallible}; /// # /// # use arcana::es::{ -/// # adapter::{self, strategy::{self, AnyContext}}, -/// # Event, Adapter as _, VersionedEvent, +/// # event::adapter::{self, strategy}, +/// # Event, EventAdapter, VersionedEvent, /// # }; /// # use derive_more::From; /// # use futures::{stream, TryStreamExt as _}; @@ -173,14 +169,10 @@ pub trait Returning { /// # File(FileEvent), /// # } /// # -/// # #[derive(Clone, Copy)] +/// # #[derive(Clone, Copy, EventAdapter)] +/// # #[adapter(into = FileDomainEvent)] /// # struct Adapter; /// # -/// # impl adapter::Returning for Adapter { -/// # type Error = Infallible; -/// # type Transformed = FileDomainEvent; -/// # } -/// # /// # impl adapter::Adapt for Adapter { /// # type Strategy = strategy::AsIs; /// # } diff --git a/core/src/es/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs similarity index 96% rename from core/src/es/adapter/transformer/mod.rs rename to core/src/es/event/adapter/transformer/mod.rs index aa1aa1d..1211724 100644 --- a/core/src/es/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -11,7 +11,7 @@ pub use strategy::Strategy; /// for every [`VersionedEvent`] involved with this [`Event`] and implement /// [`Returning`] on [`Adapter`] itself. /// -/// [`Adapter`]: crate::es::Adapter +/// [`Adapter`]: crate::es::event::Adapter /// [`Event`]: crate::es::Event /// [`Returning`]: super::Returning /// [`VersionedEvent`]: crate::es::VersionedEvent @@ -34,7 +34,7 @@ pub trait Adapt { /// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. /// See [`Adapter`] for more info. /// -/// [`Adapter`]: crate::es::Adapter +/// [`Adapter`]: crate::es::event::Adapter /// [`Custom`]: strategy::Custom /// [`Event`]: crate::es::Event /// [`Skip`]: strategy::Skip diff --git a/core/src/es/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs similarity index 95% rename from core/src/es/adapter/transformer/strategy/as_is.rs rename to core/src/es/event/adapter/transformer/strategy/as_is.rs index 3b550e1..85df15e 100644 --- a/core/src/es/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -2,7 +2,7 @@ use futures::{future, stream}; -use crate::es::{adapter, event}; +use crate::es::{event, event::adapter}; use super::{AnyContext, Strategy}; diff --git a/core/src/es/adapter/transformer/strategy/into.rs b/core/src/es/event/adapter/transformer/strategy/into.rs similarity index 90% rename from core/src/es/adapter/transformer/strategy/into.rs rename to core/src/es/event/adapter/transformer/strategy/into.rs index a406779..54ec383 100644 --- a/core/src/es/adapter/transformer/strategy/into.rs +++ b/core/src/es/event/adapter/transformer/strategy/into.rs @@ -28,7 +28,7 @@ where type Transformed = IntoEvent; type TransformedStream<'out> = stream::MapOk< InnerStrategy::TransformedStream<'out>, - fn(InnerStrategy::Transformed) -> IntoEvent, + IntoFn, >; fn transform<'me: 'out, 'ctx: 'out, 'out>( @@ -39,3 +39,5 @@ where InnerStrategy::transform(adapter, event, ctx).map_ok(IntoEvent::from) } } + +type IntoFn = fn(FromEvent) -> IntoEvent; diff --git a/core/src/es/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs similarity index 98% rename from core/src/es/adapter/transformer/strategy/mod.rs rename to core/src/es/event/adapter/transformer/strategy/mod.rs index 920cbb7..80d2252 100644 --- a/core/src/es/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -10,7 +10,7 @@ use std::borrow::Borrow; use futures::Stream; -use crate::es::{adapter, event}; +use crate::es::{event, event::adapter}; use super::{Adapt, Transformer}; diff --git a/core/src/es/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs similarity index 95% rename from core/src/es/adapter/transformer/strategy/skip.rs rename to core/src/es/event/adapter/transformer/strategy/skip.rs index 357c83a..1fb0d15 100644 --- a/core/src/es/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -2,7 +2,7 @@ use futures::stream; -use crate::es::{adapter, event}; +use crate::es::{event, event::adapter}; use super::{AnyContext, Strategy}; diff --git a/core/src/es/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs similarity index 97% rename from core/src/es/adapter/transformer/strategy/split.rs rename to core/src/es/event/adapter/transformer/strategy/split.rs index 1ecb056..a550f28 100644 --- a/core/src/es/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use futures::{stream, StreamExt as _}; -use crate::es::{adapter, event}; +use crate::es::{event, event::adapter}; use super::{AnyContext, Strategy}; diff --git a/core/src/es/event.rs b/core/src/es/event/mod.rs similarity index 98% rename from core/src/es/event.rs rename to core/src/es/event/mod.rs index 5fe3a20..ffe8ee7 100644 --- a/core/src/es/event.rs +++ b/core/src/es/event/mod.rs @@ -5,6 +5,11 @@ use std::{convert::TryFrom, marker::PhantomData, num::NonZeroU16}; use derive_more::{Deref, DerefMut, Display, Into}; use ref_cast::RefCast; +pub mod adapter; + +#[doc(inline)] +pub use self::adapter::Adapter; + /// Fully qualified name of an [`Event`]. pub type Name = &'static str; @@ -234,7 +239,7 @@ impl> Sourced> /// > considered as a fallback for [`Version`]s not covered with /// > those concrete types. /// -/// [`Adapter`]: crate::es::Adapter +/// [`Adapter`]: crate::es::event::Adapter #[derive( Clone, Copy, Debug, Deref, DerefMut, Eq, Hash, Ord, PartialEq, PartialOrd, )] @@ -279,7 +284,7 @@ where /// Marker trait for [`Versioned`] or [`Raw`] [`Event`]s. Used for [`Adapter`] /// specialization. /// -/// [`Adapter`]: crate::es::Adapter +/// [`Adapter`]: crate::es::event::Adapter pub trait VersionedOrRaw {} impl VersionedOrRaw for Raw diff --git a/core/src/es/mod.rs b/core/src/es/mod.rs index e97c1f9..15e5832 100644 --- a/core/src/es/mod.rs +++ b/core/src/es/mod.rs @@ -2,15 +2,12 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html -pub mod adapter; pub mod event; -#[doc(inline)] -pub use self::adapter::Adapter; - #[doc(inline)] pub use self::event::{ - Event, Initialized as EventInitialized, Name as EventName, Raw as RawEvent, - Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, + adapter::Adapter as EventAdapter, Event, Initialized as EventInitialized, + Name as EventName, Raw as RawEvent, Sourced as EventSourced, + Sourcing as EventSourcing, Version as EventVersion, + Versioned as VersionedEvent, }; diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 3cde0ba..1552f4d 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,15 +1,12 @@ -use std::convert::Infallible; - -use arcana::es::adapter::{self, strategy, Adapt}; +use arcana::es::{ + self, + event::adapter::{strategy, Adapt}, +}; use crate::event; -impl adapter::Returning for Adapter { - type Error = Infallible; - type Transformed = event::Chat; -} - -#[derive(Clone, Copy, Debug)] +#[derive(es::EventAdapter, Clone, Copy, Debug)] +#[adapter(into = event::Chat)] pub struct Adapter; impl Adapt for Adapter { diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index b85a604..d1917f1 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,4 +1,4 @@ -use std::{array, iter}; +use std::{array, borrow::Borrow, iter}; use arcana::es::adapter::{ self, strategy, strategy::Splitter, Adapt, AnyContext, @@ -8,12 +8,8 @@ use futures::{future, stream, StreamExt as _}; use crate::event; -impl adapter::Returning for Adapter { - type Error = serde_json::Error; - type Transformed = event::Email; -} - -#[derive(Clone, Copy, Debug)] +#[derive(es::EventAdapter, Clone, Copy, Debug)] +#[adapter(into = event::Email, err = serde_json::Error)] pub struct Adapter; impl Adapt for Adapter { diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index be94bb4..1bb1a2b 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,16 +1,15 @@ use std::{borrow::Borrow, convert::Infallible}; -use arcana::es::adapter::{self, strategy, Adapt}; +use arcana::es::{ + self, + event::adapter::{strategy, Adapt}, +}; use futures::stream; use crate::event; -impl adapter::Returning for Adapter { - type Error = Infallible; - type Transformed = event::Message; -} - -#[derive(Debug)] +#[derive(es::EventAdapter, Debug)] +#[adapter(into = event::Message)] pub struct Adapter; impl Adapt for Adapter { diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 730c998..d084359 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -40,7 +40,7 @@ pub enum EmailEvent { mod spec { use std::array; - use arcana::es::{Adapter as _, EventSourced as _}; + use arcana::es::{EventAdapter as _, EventSourced as _}; use futures::{future, stream, Stream, TryStreamExt as _}; use serde_json::json; @@ -55,7 +55,7 @@ mod spec { async fn chat_adapter() { let mut chat = Option::::None; let chat_events = chat::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &"test") .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -84,7 +84,7 @@ mod spec { async fn email_adapter() { let mut email = Option::::None; let email_events = email::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &1) .inspect_ok(|ev| email.apply(ev)) .try_collect::>() .await diff --git a/src/es/adapter/mod.rs b/src/es/adapter/mod.rs deleted file mode 100644 index b0585d3..0000000 --- a/src/es/adapter/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! [`Adapter`] definitions. - -pub mod transformer; - -#[doc(inline)] -pub use self::transformer::{ - strategy::{self, AnyContext}, - Adapt, Strategy, Transformer, -}; - -#[doc(inline)] -pub use arcana_core::es::adapter::{ - Adapter, Returning, TransformedStream, Wrapper, -}; diff --git a/src/es/adapter/transformer/strategy.rs b/src/es/adapter/transformer/strategy.rs deleted file mode 100644 index aa0d3e7..0000000 --- a/src/es/adapter/transformer/strategy.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! [`Strategy`] definition and default implementations. - -#[doc(inline)] -pub use arcana_core::es::adapter::transformer::strategy::{ - AnyContext, AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, -}; diff --git a/src/es/event/adapter/mod.rs b/src/es/event/adapter/mod.rs new file mode 100644 index 0000000..80319f7 --- /dev/null +++ b/src/es/event/adapter/mod.rs @@ -0,0 +1,15 @@ +//! [`Adapter`] definitions. + +pub mod transformer; + +#[doc(inline)] +pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; + +#[doc(inline)] +pub use arcana_core::es::event::adapter::{ + Adapter, Returning, TransformedStream, Wrapper, +}; + +#[cfg(feature = "derive")] +#[doc(inline)] +pub use arcana_codegen::es::event::Adapter; diff --git a/src/es/adapter/transformer/mod.rs b/src/es/event/adapter/transformer/mod.rs similarity index 60% rename from src/es/adapter/transformer/mod.rs rename to src/es/event/adapter/transformer/mod.rs index 867d417..71d8619 100644 --- a/src/es/adapter/transformer/mod.rs +++ b/src/es/event/adapter/transformer/mod.rs @@ -6,4 +6,4 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::adapter::transformer::{Adapt, Transformer}; +pub use arcana_core::es::event::adapter::transformer::{Adapt, Transformer}; diff --git a/src/es/event/adapter/transformer/strategy.rs b/src/es/event/adapter/transformer/strategy.rs new file mode 100644 index 0000000..2d1109f --- /dev/null +++ b/src/es/event/adapter/transformer/strategy.rs @@ -0,0 +1,7 @@ +//! [`Strategy`] definition and default implementations. + +#[doc(inline)] +pub use arcana_core::es::event::adapter::transformer::strategy::{ + AsIs, Context, Custom, Customize, InnerContext, Into, Skip, Split, + Splitter, Strategy, +}; diff --git a/src/es/event.rs b/src/es/event/mod.rs similarity index 73% rename from src/es/event.rs rename to src/es/event/mod.rs index ea931b5..682db3b 100644 --- a/src/es/event.rs +++ b/src/es/event/mod.rs @@ -1,14 +1,16 @@ //! [`Event`] machinery. +pub mod adapter; + #[doc(inline)] pub use arcana_core::es::event::{ - Event, Initial, Initialized, Name, Raw, Sourced, Sourcing, Version, - Versioned, VersionedOrRaw, + Adapter, Event, Initial, Initialized, Name, Raw, Sourced, Sourcing, + Version, Versioned, VersionedOrRaw, }; #[cfg(feature = "derive")] #[doc(inline)] -pub use arcana_codegen::es::event::{Event, Versioned}; +pub use arcana_codegen::es::event::{Adapter, Event, Versioned}; #[cfg(feature = "derive")] #[doc(hidden)] // Named so long for better error messages diff --git a/src/es/mod.rs b/src/es/mod.rs index e97c1f9..8e8ac8e 100644 --- a/src/es/mod.rs +++ b/src/es/mod.rs @@ -2,15 +2,12 @@ //! //! [Event Sourcing]: https://martinfowler.com/eaaDev/EventSourcing.html -pub mod adapter; pub mod event; -#[doc(inline)] -pub use self::adapter::Adapter; - #[doc(inline)] pub use self::event::{ - Event, Initialized as EventInitialized, Name as EventName, Raw as RawEvent, - Sourced as EventSourced, Sourcing as EventSourcing, - Version as EventVersion, Versioned as VersionedEvent, + Adapter as EventAdapter, Event, Initialized as EventInitialized, + Name as EventName, Raw as RawEvent, Sourced as EventSourced, + Sourcing as EventSourcing, Version as EventVersion, + Versioned as VersionedEvent, }; From 8c8860401edbda423e0f87faf1caa5f7eb5e7055 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 19 Oct 2021 12:28:10 +0300 Subject: [PATCH 088/104] Corrections [skip ci] --- core/src/es/event/adapter/mod.rs | 2 +- .../adapter/transformer/strategy/custom.rs | 0 examples/chat/src/storage/email.rs | 12 ++++++++---- examples/chat/src/storage/mod.rs | 4 ++-- src/es/event/adapter/transformer/strategy.rs | 3 +-- 5 files changed, 12 insertions(+), 9 deletions(-) rename core/src/es/{ => event}/adapter/transformer/strategy/custom.rs (100%) diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index 0f3b091..5521d6d 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -135,7 +135,7 @@ pub trait Returning { /// # use std::{borrow::Borrow, convert::Infallible}; /// # /// # use arcana::es::{ -/// # event::adapter::{self, strategy}, +/// # event::adapter::{self, strategy::{self, AnyContext}}, /// # Event, EventAdapter, VersionedEvent, /// # }; /// # use derive_more::From; diff --git a/core/src/es/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs similarity index 100% rename from core/src/es/adapter/transformer/strategy/custom.rs rename to core/src/es/event/adapter/transformer/strategy/custom.rs diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index d1917f1..de9a57f 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -1,7 +1,11 @@ -use std::{array, borrow::Borrow, iter}; - -use arcana::es::adapter::{ - self, strategy, strategy::Splitter, Adapt, AnyContext, +use std::{array, iter}; + +use arcana::es::{ + self, + event::adapter::{ + strategy::{self, AnyContext, Splitter}, + Adapt, + }, }; use either::Either; use futures::{future, stream, StreamExt as _}; diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index d084359..8231945 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -55,7 +55,7 @@ mod spec { async fn chat_adapter() { let mut chat = Option::::None; let chat_events = chat::Adapter - .transform_all(incoming_events(), &"test") + .transform_all(incoming_events(), &()) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -84,7 +84,7 @@ mod spec { async fn email_adapter() { let mut email = Option::::None; let email_events = email::Adapter - .transform_all(incoming_events(), &1) + .transform_all(incoming_events(), &()) .inspect_ok(|ev| email.apply(ev)) .try_collect::>() .await diff --git a/src/es/event/adapter/transformer/strategy.rs b/src/es/event/adapter/transformer/strategy.rs index 2d1109f..6c79ce8 100644 --- a/src/es/event/adapter/transformer/strategy.rs +++ b/src/es/event/adapter/transformer/strategy.rs @@ -2,6 +2,5 @@ #[doc(inline)] pub use arcana_core::es::event::adapter::transformer::strategy::{ - AsIs, Context, Custom, Customize, InnerContext, Into, Skip, Split, - Splitter, Strategy, + AnyContext, AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, }; From 08f29093eba11897179d5b43d49b41e146f241e3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 19 Oct 2021 13:38:31 +0300 Subject: [PATCH 089/104] Corrections [skip ci] --- Makefile | 4 +++- codegen/impl/src/es/event/mod.rs | 2 ++ core/src/es/event/adapter/transformer/strategy/custom.rs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dcc2449..883c0e9 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,8 @@ lint: cargo.lint # Generate crates documentation from Rust sources. # # Usage: -# make cargo.doc [crate=] [open=(yes|no)] [clean=(no|yes)] +# make cargo.doc [crate=] [private=(yes|no)] +# [open=(yes|no)] [clean=(no|yes)] cargo.doc: ifeq ($(clean),yes) @@ -41,6 +42,7 @@ ifeq ($(clean),yes) endif cargo +nightly doc $(if $(call eq,$(crate),),--workspace,-p $(crate)) \ --all-features \ + $(if $(call eq,$(private),no),,--document-private-items) \ $(if $(call eq,$(open),no),,--open) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 57b2361..2ee7618 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -171,6 +171,8 @@ impl Definition { /// /// - [`syn::Lifetime`] -> `'static`; /// - [`syn::Type`] -> `()`. + /// + /// [`syn::Lifetime`]: struct@syn::Lifetime fn substitute_generics_trivially(generics: &syn::Generics) -> TokenStream { use syn::GenericParam::{Const, Lifetime, Type}; diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index 87d262a..9460092 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -21,7 +21,7 @@ pub trait Customize { /// [`Adapter::transform_all()`][1] will expect concrete type which can be /// [`Borrow`]ed as `dyn Trait`. /// - /// [1]: crate::es::Adapter + /// [1]: crate::es::event::Adapter /// [`Borrow`]: std::borrow::Borrow type Context: ?Sized; From 81b913db0d26ae73c8da0402f2ef29134d5ada2c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 18 Nov 2021 16:34:48 +0300 Subject: [PATCH 090/104] Bootstrap working version [skip ci] --- core/src/es/event/adapter/mod.rs | 44 +++++++++++-------- .../adapter/transformer/strategy/as_is.rs | 4 +- .../event/adapter/transformer/strategy/mod.rs | 24 +++++++--- .../adapter/transformer/strategy/skip.rs | 4 +- .../adapter/transformer/strategy/split.rs | 4 +- examples/chat/src/storage/email.rs | 7 +-- examples/chat/src/storage/message.rs | 13 +++++- examples/chat/src/storage/mod.rs | 6 +-- src/es/event/adapter/transformer/strategy.rs | 3 +- 9 files changed, 67 insertions(+), 42 deletions(-) diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index 5521d6d..b1b24d4 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -13,10 +13,7 @@ use pin_project::pin_project; use ref_cast::RefCast; #[doc(inline)] -pub use self::transformer::{ - strategy::{self, AnyContext}, - Adapt, Strategy, Transformer, -}; +pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; /// Specifies result of [`Adapter`]. pub trait Returning { @@ -125,9 +122,10 @@ pub trait Returning { /// # } /// ``` /// +/// TODO: reconsider /// In case you want to use custom context, it should implement [`Borrow`] /// `dyn `[`Strategy::Context`]s for all used [`Strategies`]. Default ones use -/// [`AnyContext`]. +/// `AnyContext`. /// /// ```rust /// # #![feature(generic_associated_types)] @@ -135,7 +133,7 @@ pub trait Returning { /// # use std::{borrow::Borrow, convert::Infallible}; /// # /// # use arcana::es::{ -/// # event::adapter::{self, strategy::{self, AnyContext}}, +/// # event::adapter::{self, strategy}, /// # Event, EventAdapter, VersionedEvent, /// # }; /// # use derive_more::From; @@ -193,12 +191,6 @@ pub trait Returning { /// # ]); /// struct CustomContext; /// -/// impl Borrow for CustomContext { -/// fn borrow(&self) -> &(dyn AnyContext + 'static) { -/// self -/// } -/// } -/// /// let transformed = Adapter /// .transform_all(events, &CustomContext) /// .try_collect::>() @@ -217,7 +209,6 @@ pub trait Returning { /// # } /// ``` /// -/// [`AnyContext`]: transformer::strategy::AnyContext /// [`Borrow`]: std::borrow::Borrow /// [`Error`]: Self::Error /// [`Event`]: crate::es::Event @@ -268,10 +259,21 @@ where A: Returning, Ctx: ?Sized + 'ctx, Events: Stream, - Wrapper: Transformer<'ctx, Events::Item, Ctx>, - A::Transformed: - From< as Transformer<'ctx, Events::Item, Ctx>>::Transformed>, - A::Error: From< as Transformer<'ctx, Events::Item, Ctx>>::Error>, + Wrapper: Transformer<'ctx, Events::Item, strategy::Context>, + A::Transformed: From< + as Transformer< + 'ctx, + Events::Item, + strategy::Context, + >>::Transformed, + >, + A::Error: From< + as Transformer< + 'ctx, + Events::Item, + strategy::Context, + >>::Error, + >, { type Error = ::Error; type Transformed = ::Transformed; @@ -281,14 +283,18 @@ where Ctx: 'ctx, Events: 'out, Self: 'out, - = TransformedStream<'ctx, 'out, Wrapper, Events, Ctx>; + = TransformedStream<'ctx, 'out, Wrapper, Events, strategy::Context>; fn transform_all<'me: 'out, 'out>( &'me self, events: Events, context: &'ctx Ctx, ) -> Self::TransformedStream<'out> { - TransformedStream::new(RefCast::ref_cast(self), events, context) + TransformedStream::new( + RefCast::ref_cast(self), + events, + RefCast::ref_cast(context), + ) } } diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index 85df15e..25fec92 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -4,7 +4,7 @@ use futures::{future, stream}; use crate::es::{event, event::adapter}; -use super::{AnyContext, Strategy}; +use super::Strategy; /// [`Strategy`] for passing [`Event`]s as is, without any conversions. /// @@ -18,7 +18,7 @@ where Adapter::Error: 'static, Event: event::VersionedOrRaw + 'static, { - type Context = dyn AnyContext; + type Context = (); type Error = Adapter::Error; type Transformed = Event; type TransformedStream<'o> = diff --git a/core/src/es/event/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs index 80d2252..151a0df 100644 --- a/core/src/es/event/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -9,6 +9,7 @@ pub mod split; use std::borrow::Borrow; use futures::Stream; +use ref_cast::RefCast; use crate::es::{event, event::adapter}; @@ -105,13 +106,24 @@ where } } -/// [`Strategy::Context`] implemented for every type. -pub trait AnyContext {} +/// TODO +#[derive(Clone, Copy, Debug, RefCast)] +#[repr(transparent)] +pub struct Context(pub T); -impl AnyContext for T {} +/// TODO +#[derive(Clone, Copy, Debug, RefCast)] +#[repr(transparent)] +pub struct InnerContext(pub T); -impl Borrow<(dyn AnyContext + 'static)> for () { - fn borrow(&self) -> &(dyn AnyContext + 'static) { - self +impl Borrow<()> for Context { + fn borrow(&self) -> &() { + &() + } +} + +impl> Borrow> for Context { + fn borrow(&self) -> &InnerContext { + RefCast::ref_cast(self.0.borrow()) } } diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index 1fb0d15..b8828fe 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -4,7 +4,7 @@ use futures::stream; use crate::es::{event, event::adapter}; -use super::{AnyContext, Strategy}; +use super::Strategy; /// [`Strategy`] for skipping [`Event`]s. /// @@ -19,7 +19,7 @@ where Adapter::Transformed: 'static, Adapter::Error: 'static, { - type Context = dyn AnyContext; + type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; type TransformedStream<'o> = diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index a550f28..525703d 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -6,7 +6,7 @@ use futures::{stream, StreamExt as _}; use crate::es::{event, event::adapter}; -use super::{AnyContext, Strategy}; +use super::Strategy; /// [`Strategy`] for splitting single [`Event`] into multiple. Implement /// [`Splitter`] to define splitting logic. @@ -38,7 +38,7 @@ where Event: event::VersionedOrRaw, IntoEvent: 'static, { - type Context = dyn AnyContext; + type Context = (); type Error = Adapter::Error; type Transformed = ::Item; type TransformedStream<'o> = SplitStream; diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index de9a57f..6e5ef4c 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -2,10 +2,7 @@ use std::{array, iter}; use arcana::es::{ self, - event::adapter::{ - strategy::{self, AnyContext, Splitter}, - Adapt, - }, + event::adapter::{strategy, strategy::Splitter, Adapt}, }; use either::Either; use futures::{future, stream, StreamExt as _}; @@ -89,7 +86,7 @@ impl event::Raw, > for Adapter { - type Context = dyn AnyContext; + type Context = (); type Error = serde_json::Error; type Transformed = Either; type TransformedStream<'out> = CustomizedStream; diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index 1bb1a2b..efb7dc6 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -2,7 +2,10 @@ use std::{borrow::Borrow, convert::Infallible}; use arcana::es::{ self, - event::adapter::{strategy, Adapt}, + event::adapter::{ + strategy::{self, InnerContext}, + Adapt, + }, }; use futures::stream; @@ -48,7 +51,7 @@ impl Adapt> // Basically same as Skip, but with additional Context bounds. impl strategy::Customize for Adapter { - type Context = dyn Bound; + type Context = InnerContext; type Error = Infallible; type Transformed = event::Message; type TransformedStream<'out> = @@ -76,3 +79,9 @@ impl Borrow<(dyn Bound + 'static)> for () { self } } + +impl Borrow<(dyn Bound + 'static)> for &'static str { + fn borrow(&self) -> &(dyn Bound + 'static) { + &() + } +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 8231945..fd874ba 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -55,7 +55,7 @@ mod spec { async fn chat_adapter() { let mut chat = Option::::None; let chat_events = chat::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &"test") .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -84,7 +84,7 @@ mod spec { async fn email_adapter() { let mut email = Option::::None; let email_events = email::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &"test") .inspect_ok(|ev| email.apply(ev)) .try_collect::>() .await @@ -138,7 +138,7 @@ mod spec { async fn message_adapter() { let mut message = Option::::None; let message_events = message::Adapter - .transform_all(incoming_events(), &()) + .transform_all(incoming_events(), &"test") .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await diff --git a/src/es/event/adapter/transformer/strategy.rs b/src/es/event/adapter/transformer/strategy.rs index 6c79ce8..2d1109f 100644 --- a/src/es/event/adapter/transformer/strategy.rs +++ b/src/es/event/adapter/transformer/strategy.rs @@ -2,5 +2,6 @@ #[doc(inline)] pub use arcana_core::es::event::adapter::transformer::strategy::{ - AnyContext, AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, + AsIs, Context, Custom, Customize, InnerContext, Into, Skip, Split, + Splitter, Strategy, }; From 93710a51037744c0f8b760e7361e463df64a1b8c Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 18 Nov 2021 16:55:53 +0300 Subject: [PATCH 091/104] fix macros after 1.56 [skip ci] --- codegen/impl/src/es/event/adapter.rs | 2 +- codegen/impl/src/es/event/mod.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/codegen/impl/src/es/event/adapter.rs b/codegen/impl/src/es/event/adapter.rs index e888879..2d65bba 100644 --- a/codegen/impl/src/es/event/adapter.rs +++ b/codegen/impl/src/es/event/adapter.rs @@ -89,7 +89,7 @@ impl Definition { quote! { #[automatically_derived] impl #impl_gens ::arcana::es::event::adapter::Returning for - #ty#ty_gens + #ty #ty_gens #where_clause { type Error = #error; diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 45a551a..85af1b1 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -395,8 +395,8 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_gen ::arcana::es::event::adapter::Transformer< - '__ctx, #event#type_gen, __Ctx + impl #impl_gen ::arcana::es::event::adapter::Transformer< + '__ctx, #event #type_gen, __Ctx > for ::arcana::es::event::adapter::Wrapper<__A> #where_clause { type Error = <__A as ::arcana::es::event::adapter::Returning>:: @@ -408,10 +408,10 @@ impl Definition { fn transform<'me, 'out>( &'me self, - __event: #event#type_gen, + __event: #event #type_gen, __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, #event #type_gen, __Ctx>>:: TransformedStream<'out> where 'me: 'out, @@ -528,10 +528,10 @@ impl Definition { >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, #event #ty_gen, __Ctx>>:: Transformed, >::Error, + Transformer<'__ctx, #event #ty_gen, __Ctx>>::Error, > > } @@ -627,7 +627,7 @@ impl Definition { }); quote! { - #event#turbofish_gens::#variant_ident(__event) => { + #event #turbofish_gens::#variant_ident(__event) => { #transformed_stream }, } From df1bcd391d504a3664d397ce8d64372b222f7347 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 11:55:39 +0300 Subject: [PATCH 092/104] Corrections --- codegen/impl/src/es/event/adapter.rs | 22 +-- codegen/impl/src/es/event/mod.rs | 16 +- codegen/impl/src/lib.rs | 2 + codegen/shim/src/lib.rs | 10 +- codegen/src/lib.rs | 2 + core/Cargo.toml | 9 +- core/src/es/event/adapter/mod.rs | 142 ++++++++++++++---- .../adapter/transformer/strategy/as_is.rs | 9 +- .../adapter/transformer/strategy/custom.rs | 15 +- .../adapter/transformer/strategy/into.rs | 1 + .../event/adapter/transformer/strategy/mod.rs | 33 +--- .../adapter/transformer/strategy/skip.rs | 2 + .../adapter/transformer/strategy/split.rs | 3 + core/src/es/event/mod.rs | 4 +- core/src/lib.rs | 1 + core/src/spell.rs | 9 ++ examples/chat/src/storage/message.rs | 35 ++--- examples/chat/src/storage/mod.rs | 6 +- src/es/event/adapter/mod.rs | 2 +- src/es/event/adapter/transformer/strategy.rs | 3 +- src/lib.rs | 7 + 21 files changed, 212 insertions(+), 121 deletions(-) create mode 100644 core/src/spell.rs diff --git a/codegen/impl/src/es/event/adapter.rs b/codegen/impl/src/es/event/adapter.rs index 2d65bba..5f49732 100644 --- a/codegen/impl/src/es/event/adapter.rs +++ b/codegen/impl/src/es/event/adapter.rs @@ -22,15 +22,15 @@ pub fn derive(input: TokenStream) -> syn::Result { /// Helper attributes of `#[derive(event::Adapter)]` macro. #[derive(Debug, Default, ParseAttrs)] pub struct Attrs { - /// [`Returning::Transformed`][1] associated type. + /// [`Returning::Transformed`][0] associated type. /// - /// [1]: arcana_core::es::event::adapter::Returning::Transformed + /// [0]: arcana_core::es::event::adapter::Returning::Transformed #[parse(value, alias = into)] pub transformed: Required, - /// [`Returning::Error`][1] associated type. + /// [`Returning::Error`][0] associated type. /// - /// [1]: arcana_core::es::event::adapter::Returning::Error + /// [0]: arcana_core::es::event::adapter::Returning::Error #[parse(value, alias = err)] pub error: Option, } @@ -48,14 +48,14 @@ pub struct Definition { /// [`syn::Generics`] of this type. pub generics: syn::Generics, - /// [`Returning::Transformed`][1] associated type. + /// [`Returning::Transformed`][0] associated type. /// - /// [1]: arcana_core::es::event::adapter::Returning::Transformed + /// [0]: arcana_core::es::event::adapter::Returning::Transformed pub transformed: syn::Type, - /// [`Returning::Error`][1] associated type. + /// [`Returning::Error`][0] associated type. /// - /// [1]: arcana_core::es::event::adapter::Returning::Error + /// [0]: arcana_core::es::event::adapter::Returning::Error pub error: syn::Type, } @@ -71,15 +71,15 @@ impl TryFrom for Definition { transformed: attrs.transformed.into_inner(), error: attrs .error - .unwrap_or_else(|| parse_quote!(::std::convert::Infallible)), + .unwrap_or_else(|| parse_quote! { ::std::convert::Infallible }), }) } } impl Definition { - /// Generates code to derive [`Returning`][1] trait. + /// Generates code to derive [`Returning`][0] trait. /// - /// [1]: arcana_core::es::event::adapter::Returning + /// [0]: arcana_core::es::event::adapter::Returning #[must_use] pub fn impl_returning(&self) -> TokenStream { let ty = &self.ident; diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 85af1b1..fb2ee71 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -376,7 +376,7 @@ impl Definition { /// [`Adapter`][2], which can transform every enum variant. /// /// [0]: arcana_core::es::event::adapter::Transformer - /// [1]: arcana_core::es::event::adapter::Wrapper + /// [1]: arcana_core::es::event::adapter::Adapted /// [2]: arcana_core::es::event::Adapter #[must_use] pub fn impl_transformer(&self) -> TokenStream { @@ -397,7 +397,7 @@ impl Definition { #[automatically_derived] impl #impl_gen ::arcana::es::event::adapter::Transformer< '__ctx, #event #type_gen, __Ctx - > for ::arcana::es::event::adapter::Wrapper<__A> #where_clause + > for ::arcana::es::event::adapter::Adapted<__A> #where_clause { type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; @@ -429,7 +429,7 @@ impl Definition { /// Generates [`syn::Generics`] to for [wrapped][0] [`Adapter`][1], which /// [`transform`][2]s every enum variant. /// - /// [0]: arcana_core::es::event::adapter::Wrapper + /// [0]: arcana_core::es::event::adapter::Adapted /// [1]: arcana_core::es::event::Adapter /// [2]: arcana_core::es::event::adapter::Transformer::transform #[must_use] @@ -544,12 +544,12 @@ impl Definition { .fold(None, |acc, ty| { let variant_stream = transformed_stream(ty); Some( - acc.map(|acc| { + acc.map(|stream| { quote! { ::arcana::es::event::codegen::futures::future:: Either< #variant_stream, - #acc, + #stream, > } }) @@ -698,7 +698,7 @@ mod spec { #[automatically_derived] impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Wrapper<__A> + > for ::arcana::es::event::adapter::Adapted<__A> where __A: ::arcana::es::event::adapter::Returning, Self: @@ -992,7 +992,7 @@ mod spec { #[automatically_derived] impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: Transformer<'__ctx, Event<'a, F, C>, __Ctx> for - ::arcana::es::event::adapter::Wrapper<__A> + ::arcana::es::event::adapter::Adapted<__A> where __A: ::arcana::es::event::adapter::Returning, Self: @@ -1291,7 +1291,7 @@ mod spec { #[automatically_derived] impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Wrapper<__A> + > for ::arcana::es::event::adapter::Adapted<__A> where __A: ::arcana::es::event::adapter::Returning, Self: diff --git a/codegen/impl/src/lib.rs b/codegen/impl/src/lib.rs index e93b4cb..345c018 100644 --- a/codegen/impl/src/lib.rs +++ b/codegen/impl/src/lib.rs @@ -90,3 +90,5 @@ pub mod es; // Only for generating documentation. #[cfg(feature = "doc")] use arcana_core as _; +#[cfg(feature = "doc")] +use futures as _; diff --git a/codegen/shim/src/lib.rs b/codegen/shim/src/lib.rs index bd2169b..f37856e 100644 --- a/codegen/shim/src/lib.rs +++ b/codegen/shim/src/lib.rs @@ -263,9 +263,13 @@ pub fn derive_versioned_event(input: TokenStream) -> TokenStream { /// ```rust /// # use arcana::es::event; /// # -/// #[derive(event::Versioned)] -/// #[event(name = "event", version = 1)] -/// struct Event; +/// # #[derive(event::Versioned)] +/// # #[event(name = "event", version = 1)] +/// # struct Event; +/// # +/// #[derive(event::Adapter, Clone, Copy, Debug)] +/// #[adapter(into = Event)] +/// struct Adapter; /// ``` /// /// [`Adapter`]: arcana_core::es::event::Adapter diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index a99c57c..413991c 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -90,5 +90,7 @@ pub mod es; // Only for generating documentation. #[cfg(feature = "doc")] use arcana_core as _; +#[cfg(feature = "doc")] +use futures as _; pub use static_assertions as sa; diff --git a/core/Cargo.toml b/core/Cargo.toml index 4a2dabc..74856ed 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,15 +18,14 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -codegen = ["sealed"] # only enables codegen glue -es = ["derive_more"] +codegen = [] # only enables codegen glue +es = ["derive_more", "futures", "pin-project"] [dependencies] derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "from", "into"], default-features = false, optional = true } -futures = { version = "0.3.11", default-features = false } -pin-project = "1.0" +futures = { version = "0.3.11", default-features = false, optional = true } +pin-project = { version = "1.0", optional = true } ref-cast = "1.0" -sealed = { version = "0.3", optional = true } [dev-dependencies] arcana = { version = "0.1.0-dev", path = "..", features = ["derive", "es"] } diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index b1b24d4..81224d2 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -3,19 +3,25 @@ pub mod transformer; use std::{ + borrow::Borrow, fmt::{Debug, Formatter}, pin::Pin, - task::{Context, Poll}, + task, }; use futures::{future, stream, Stream, StreamExt as _}; use pin_project::pin_project; use ref_cast::RefCast; +use crate::spell; + #[doc(inline)] pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; /// Specifies result of [`Adapter`]. +/// +/// Consider using [`Adapter`] derive macro instead of implementing this +/// manually. pub trait Returning { /// Error of this [`Adapter`]. type Error; @@ -68,7 +74,7 @@ pub trait Returning { /// /// // Repository-level Event, which is loaded from some Event Store and /// // includes legacy Events. -/// #[derive(Clone, Copy, Debug, Event, PartialEq, From)] +/// #[derive(Clone, Copy, Debug, Event, From, PartialEq)] /// enum RepositoryEvent { /// FileV1(FileEventV1), /// File(FileEvent), @@ -122,19 +128,21 @@ pub trait Returning { /// # } /// ``` /// -/// TODO: reconsider -/// In case you want to use custom context, it should implement [`Borrow`] -/// `dyn `[`Strategy::Context`]s for all used [`Strategies`]. Default ones use -/// `AnyContext`. +/// In case some of your [`Strategies`] are [`Custom`] with non-`()` +/// [`Customize::Context`], provided `context` should be able to be [`Borrow`]ed +/// as `dyn Trait`. /// /// ```rust /// # #![feature(generic_associated_types)] /// # /// # use std::{borrow::Borrow, convert::Infallible}; /// # -/// # use arcana::es::{ -/// # event::adapter::{self, strategy}, -/// # Event, EventAdapter, VersionedEvent, +/// # use arcana::{ +/// # es::{ +/// # event::adapter::{self, strategy}, +/// # Event, EventAdapter, VersionedEvent, +/// # }, +/// # spell, /// # }; /// # use derive_more::From; /// # use futures::{stream, TryStreamExt as _}; @@ -179,9 +187,46 @@ pub trait Returning { /// # type Strategy = strategy::Into; /// # } /// # -/// # impl adapter::Adapt for Adapter { -/// # type Strategy = strategy::Skip; -/// # } +/// impl adapter::Adapt for Adapter { +/// type Strategy = strategy::Custom; +/// } +/// +/// impl strategy::Customize for Adapter { +/// type Context = spell::Borrowed< +/// dyn Empty> +/// >; +/// type Error = Infallible; +/// type Transformed = FileDomainEvent; +/// type TransformedStream<'o> = stream::Empty< +/// Result +/// >; +/// +/// fn transform<'me: 'out, 'ctx: 'out, 'out>( +/// &'me self, +/// _event: ChatEvent, +/// context: &'ctx Self::Context +/// ) -> Self::TransformedStream<'out> { +/// context.stream() +/// } +/// +/// } +/// +/// pub struct EmptyProvider; +/// +/// pub trait Empty { +/// fn stream(&self) -> stream::Empty { +/// stream::empty() +/// } +/// } +/// +/// impl Empty for EmptyProvider {} +/// +/// impl Borrow<(dyn Empty + 'static)> for EmptyProvider { +/// fn borrow(&self) -> &(dyn Empty + 'static) { +/// self +/// } +/// } +/// /// # /// # let assertion = async { /// # let events = stream::iter::<[RepositoryEvent; 3]>([ @@ -189,10 +234,8 @@ pub trait Returning { /// # FileEvent.into(), /// # ChatEvent.into(), /// # ]); -/// struct CustomContext; -/// /// let transformed = Adapter -/// .transform_all(events, &CustomContext) +/// .transform_all(events, &EmptyProvider) /// .try_collect::>() /// .await /// .unwrap(); @@ -210,6 +253,8 @@ pub trait Returning { /// ``` /// /// [`Borrow`]: std::borrow::Borrow +/// [`Custom`]: transformer::strategy::Custom +/// [`Customize::Context`]: transformer::strategy::Customize::Context /// [`Error`]: Self::Error /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip @@ -259,19 +304,19 @@ where A: Returning, Ctx: ?Sized + 'ctx, Events: Stream, - Wrapper: Transformer<'ctx, Events::Item, strategy::Context>, + Adapted: Transformer<'ctx, Events::Item, Context>, A::Transformed: From< - as Transformer< + as Transformer< 'ctx, Events::Item, - strategy::Context, + Context, >>::Transformed, >, A::Error: From< - as Transformer< + as Transformer< 'ctx, Events::Item, - strategy::Context, + Context, >>::Error, >, { @@ -283,7 +328,7 @@ where Ctx: 'ctx, Events: 'out, Self: 'out, - = TransformedStream<'ctx, 'out, Wrapper, Events, strategy::Context>; + = TransformedStream<'ctx, 'out, Adapted, Events, Context>; fn transform_all<'me: 'out, 'out>( &'me self, @@ -298,15 +343,36 @@ where } } +/// Wrapper around `context` in [`Adapter::transform_all()`] method use in pair +/// with [`spell::Borrowed`] to hack around orphan rules. Shouldn't be used +/// manually. +#[derive(Clone, Copy, Debug, RefCast)] +#[repr(transparent)] +pub struct Context(pub T); + +impl Borrow<()> for Context { + fn borrow(&self) -> &() { + &() + } +} + +impl> Borrow> + for Context +{ + fn borrow(&self) -> &spell::Borrowed { + RefCast::ref_cast(self.0.borrow()) + } +} + /// Wrapper type for [`Adapter`] to satisfy orphan rules on [`Event`] derive /// macro. Shouldn't be used manually. /// /// [`Event`]: crate::es::Event #[derive(Debug, RefCast)] #[repr(transparent)] -pub struct Wrapper(pub A); +pub struct Adapted(pub A); -impl Returning for Wrapper +impl Returning for Adapted where A: Returning, { @@ -315,6 +381,9 @@ where } /// [`Stream`] for [`Adapter`] blanket impl. +/// +/// Basically applies [`Transformer::transform()`] to every element of +/// [`Adapter`]s `Events` [`Stream`] and flattens it. #[pin_project] pub struct TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where @@ -322,12 +391,21 @@ where Ctx: ?Sized, Events: Stream, { + /// [`Stream`] of [`Event`]s to [`Transformer::transform()`]. + /// + /// [`Event`]: crate::es::Event #[pin] events: Events, + + /// [`Transformer::transform()`] [`Stream`] to flatten. #[pin] transformed_stream: AdapterTransformedStream<'ctx, 'out, Events::Item, Adapter, Ctx>, + + /// [`Adapter`] implementor reference. adapter: &'out Adapter, + + /// [`Adapter`]'s `Context` reference. context: &'ctx Ctx, } @@ -347,6 +425,7 @@ where } } +/// Alias for [`TransformedStream::transformed_stream`]. type AdapterTransformedStream<'ctx, 'out, Event, Adapter, Ctx> = future::Either< >::TransformedStream<'out>, stream::Empty< @@ -364,6 +443,7 @@ where Ctx: ?Sized, Events: Stream, { + /// Creates a new [`TransformedStream`]. fn new(adapter: &'out Adapter, events: Events, context: &'ctx Ctx) -> Self where 'ctx: 'out, @@ -396,26 +476,26 @@ where fn poll_next( self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + cx: &mut task::Context<'_>, + ) -> task::Poll> { let mut this = self.project(); loop { - let res = + let flattened_res = futures::ready!(this.transformed_stream.as_mut().poll_next(cx)); - if let Some(ev) = res { - return Poll::Ready(Some( + if let Some(ev) = flattened_res { + return task::Poll::Ready(Some( ev.map(Into::into).map_err(Into::into), )); } - let res = futures::ready!(this.events.as_mut().poll_next(cx)); - if let Some(event) = res { + let outer_res = futures::ready!(this.events.as_mut().poll_next(cx)); + if let Some(event) = outer_res { let new_stream = Adapter::transform(*this.adapter, event, *this.context); this.transformed_stream.set(new_stream.left_stream()); } else { - return Poll::Ready(None); + return task::Poll::Ready(None); } } } diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index 25fec92..99ec1ad 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -2,9 +2,10 @@ use futures::{future, stream}; -use crate::es::{event, event::adapter}; - -use super::Strategy; +use super::{ + event::{self, adapter}, + Strategy, +}; /// [`Strategy`] for passing [`Event`]s as is, without any conversions. /// @@ -21,9 +22,11 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Event; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> = stream::Once>>; + #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( _: &Adapter, event: Event, diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index 9460092..8d41997 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -2,9 +2,7 @@ use futures::Stream; -use crate::es::event; - -use super::Strategy; +use super::{event, Strategy}; /// [`Strategy`] for some custom conversion provided by [`Customize`]. #[derive(Clone, Copy, Debug)] @@ -17,12 +15,15 @@ pub struct Custom; pub trait Customize { /// Context of this [`Custom`] [`Strategy`]. /// - /// In real world this is usually `dyn Trait`. In that case, - /// [`Adapter::transform_all()`][1] will expect concrete type which can be - /// [`Borrow`]ed as `dyn Trait`. + /// This should be one of 2 things: + /// - `()` + /// In case you want to accept any struct as a `context`. + /// - [`spell::Borrowed`]`` + /// In that case `context` should be able to be [`Borrow`]ed as + /// `dyn Trait`. /// - /// [1]: crate::es::event::Adapter /// [`Borrow`]: std::borrow::Borrow + /// [`spell::Borrowed`]: crate::spell::Borrowed type Context: ?Sized; /// Error of this [`Strategy`]. diff --git a/core/src/es/event/adapter/transformer/strategy/into.rs b/core/src/es/event/adapter/transformer/strategy/into.rs index 54ec383..ecf1c2a 100644 --- a/core/src/es/event/adapter/transformer/strategy/into.rs +++ b/core/src/es/event/adapter/transformer/strategy/into.rs @@ -40,4 +40,5 @@ where } } +/// Alias for [`From::from()`]. type IntoFn = fn(FromEvent) -> IntoEvent; diff --git a/core/src/es/event/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs index 151a0df..d12f878 100644 --- a/core/src/es/event/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -9,7 +9,6 @@ pub mod split; use std::borrow::Borrow; use futures::Stream; -use ref_cast::RefCast; use crate::es::{event, event::adapter}; @@ -30,11 +29,11 @@ pub use self::{ pub trait Strategy { /// Context of this [`Strategy`]. /// - /// In real world this is usually `dyn Trait`. In that case, - /// [`Adapter::transform_all()`][1] will expect type which can be - /// [`Borrow`]ed as `dyn Trait`. + /// [`Adapter::transform_all()`][0] will expect type which can be + /// [`Borrow`]ed as [`Context`]. /// - /// [1]: adapter::Adapter::transform_all + /// [0]: adapter::Adapter::transform_all + /// [`Context`]: Self::Context type Context: ?Sized; /// Error of this [`Strategy`]. @@ -68,7 +67,7 @@ pub trait Strategy { } impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> - for adapter::Wrapper + for adapter::Adapted where Event: event::VersionedOrRaw, Adapter: Adapt + adapter::Returning, @@ -105,25 +104,3 @@ where ) } } - -/// TODO -#[derive(Clone, Copy, Debug, RefCast)] -#[repr(transparent)] -pub struct Context(pub T); - -/// TODO -#[derive(Clone, Copy, Debug, RefCast)] -#[repr(transparent)] -pub struct InnerContext(pub T); - -impl Borrow<()> for Context { - fn borrow(&self) -> &() { - &() - } -} - -impl> Borrow> for Context { - fn borrow(&self) -> &InnerContext { - RefCast::ref_cast(self.0.borrow()) - } -} diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index b8828fe..e7c73d7 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -22,9 +22,11 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> = stream::Empty>; + #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( _: &Adapter, _: Event, diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index 525703d..a668545 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -41,8 +41,10 @@ where type Context = (); type Error = Adapter::Error; type Transformed = ::Item; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> = SplitStream; + #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &Adapter, event: Event, @@ -52,6 +54,7 @@ where } } +/// [`Strategy::TransformedStream`] for [`Split`]. type SplitStream = stream::Map< stream::Iter<>::Iterator>, fn( diff --git a/core/src/es/event/mod.rs b/core/src/es/event/mod.rs index ee61de1..cca9053 100644 --- a/core/src/es/event/mod.rs +++ b/core/src/es/event/mod.rs @@ -259,7 +259,7 @@ pub struct Raw { impl Raw { /// Creates a new [`Raw`]. #[must_use] - pub fn new(data: Data, version: Version) -> Self { + pub const fn new(data: Data, version: Version) -> Self { Self { data, version, @@ -290,7 +290,7 @@ pub trait VersionedOrRaw {} impl VersionedOrRaw for Raw where Ev: ?Sized, - Raw: Event, + Self: Event, { } diff --git a/core/src/lib.rs b/core/src/lib.rs index fda97d9..5b6b0ad 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -89,3 +89,4 @@ #[cfg(feature = "es")] pub mod es; +pub mod spell; diff --git a/core/src/spell.rs b/core/src/spell.rs new file mode 100644 index 0000000..a8a333a --- /dev/null +++ b/core/src/spell.rs @@ -0,0 +1,9 @@ +//! Helpers with a little bit of type magic 🪄. + +use derive_more::Deref; +use ref_cast::RefCast; + +/// Helper to hack around specialization. +#[derive(Clone, Copy, Debug, Deref, RefCast)] +#[repr(transparent)] +pub struct Borrowed(pub T); diff --git a/examples/chat/src/storage/message.rs b/examples/chat/src/storage/message.rs index efb7dc6..a8abd83 100644 --- a/examples/chat/src/storage/message.rs +++ b/examples/chat/src/storage/message.rs @@ -1,11 +1,11 @@ use std::{borrow::Borrow, convert::Infallible}; -use arcana::es::{ - self, - event::adapter::{ - strategy::{self, InnerContext}, - Adapt, +use arcana::{ + es::{ + self, + event::adapter::{strategy, Adapt}, }, + spell, }; use futures::stream; @@ -51,7 +51,8 @@ impl Adapt> // Basically same as Skip, but with additional Context bounds. impl strategy::Customize for Adapter { - type Context = InnerContext; + type Context = + spell::Borrowed>>; type Error = Infallible; type Transformed = event::Message; type TransformedStream<'out> = @@ -60,28 +61,28 @@ impl strategy::Customize for Adapter { fn transform<'me, 'ctx, 'out>( &'me self, _event: event::chat::public::Created, - _context: &'ctx Self::Context, + context: &'ctx Self::Context, ) -> Self::TransformedStream<'out> where 'me: 'out, 'ctx: 'out, { - stream::empty() + context.stream() } } -pub trait Bound {} - -impl Bound for () {} +pub struct EmptyProvider; -impl Borrow<(dyn Bound + 'static)> for () { - fn borrow(&self) -> &(dyn Bound + 'static) { - self +pub trait Empty { + fn stream(&self) -> stream::Empty { + stream::empty() } } -impl Borrow<(dyn Bound + 'static)> for &'static str { - fn borrow(&self) -> &(dyn Bound + 'static) { - &() +impl Empty for EmptyProvider {} + +impl Borrow<(dyn Empty + 'static)> for EmptyProvider { + fn borrow(&self) -> &(dyn Empty + 'static) { + self } } diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index fd874ba..89ab89d 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -55,7 +55,7 @@ mod spec { async fn chat_adapter() { let mut chat = Option::::None; let chat_events = chat::Adapter - .transform_all(incoming_events(), &"test") + .transform_all(incoming_events(), &()) .inspect_ok(|ev| chat.apply(ev)) .try_collect::>() .await @@ -126,7 +126,7 @@ mod spec { ))); let result = email::Adapter - .transform_all(stream::once(future::ready(corrupted_event)), &()) + .transform_all(stream::once(future::ready(corrupted_event)), &42) .try_collect::>() .await; @@ -138,7 +138,7 @@ mod spec { async fn message_adapter() { let mut message = Option::::None; let message_events = message::Adapter - .transform_all(incoming_events(), &"test") + .transform_all(incoming_events(), &message::EmptyProvider) .inspect_ok(|ev| message.apply(ev)) .try_collect::>() .await diff --git a/src/es/event/adapter/mod.rs b/src/es/event/adapter/mod.rs index 80319f7..2323dc4 100644 --- a/src/es/event/adapter/mod.rs +++ b/src/es/event/adapter/mod.rs @@ -7,7 +7,7 @@ pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; #[doc(inline)] pub use arcana_core::es::event::adapter::{ - Adapter, Returning, TransformedStream, Wrapper, + Adapted, Adapter, Returning, TransformedStream, }; #[cfg(feature = "derive")] diff --git a/src/es/event/adapter/transformer/strategy.rs b/src/es/event/adapter/transformer/strategy.rs index 2d1109f..b6aaace 100644 --- a/src/es/event/adapter/transformer/strategy.rs +++ b/src/es/event/adapter/transformer/strategy.rs @@ -2,6 +2,5 @@ #[doc(inline)] pub use arcana_core::es::event::adapter::transformer::strategy::{ - AsIs, Context, Custom, Customize, InnerContext, Into, Skip, Split, - Splitter, Strategy, + AsIs, Custom, Customize, Into, Skip, Split, Splitter, Strategy, }; diff --git a/src/lib.rs b/src/lib.rs index 580fbec..164a848 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,3 +85,10 @@ #[cfg(feature = "es")] pub mod es; + +pub mod spell { + //! Helpers with a little bit of type magic 🪄. + + #[doc(inline)] + pub use arcana_core::spell::Borrowed; +} From 23cec975930bbbf78e3be7d0b0daf4ad1b2d2e98 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 11:59:17 +0300 Subject: [PATCH 093/104] Corrections --- core/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 74856ed..00c89b5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,10 +19,10 @@ rustdoc-args = ["--cfg", "docsrs"] [features] codegen = [] # only enables codegen glue -es = ["derive_more", "futures", "pin-project"] +es = ["futures", "pin-project"] [dependencies] -derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "from", "into"], default-features = false, optional = true } +derive_more = { version = "0.99", features = ["deref", "deref_mut", "display", "from", "into"], default-features = false } futures = { version = "0.3.11", default-features = false, optional = true } pin-project = { version = "1.0", optional = true } ref-cast = "1.0" From 42889ebb7ac0918d8ef84aee2d623d99a3ae0fc8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 16:27:50 +0300 Subject: [PATCH 094/104] Dance around new GAT rules --- codegen/impl/src/es/event/mod.rs | 461 +++++++++++++----- core/src/es/event/adapter/mod.rs | 51 +- core/src/es/event/adapter/transformer/mod.rs | 54 +- .../adapter/transformer/strategy/as_is.rs | 6 +- .../adapter/transformer/strategy/custom.rs | 9 +- .../adapter/transformer/strategy/into.rs | 5 +- .../event/adapter/transformer/strategy/mod.rs | 40 +- .../adapter/transformer/strategy/skip.rs | 6 +- .../adapter/transformer/strategy/split.rs | 5 +- src/es/event/adapter/mod.rs | 4 +- src/es/event/adapter/transformer/mod.rs | 4 +- 11 files changed, 473 insertions(+), 172 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index fb2ee71..73703f7 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -395,7 +395,7 @@ impl Definition { quote! { #[automatically_derived] - impl #impl_gen ::arcana::es::event::adapter::Transformer< + impl #impl_gen ::arcana::es::event::adapter::TransformerTypes< '__ctx, #event #type_gen, __Ctx > for ::arcana::es::event::adapter::Adapted<__A> #where_clause { @@ -404,14 +404,24 @@ impl Definition { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = #transformed; + type TransformedStream<'out> + where + Self: 'out + = #transformed; + } + + #[automatically_derived] + impl #impl_gen ::arcana::es::event::adapter::Transformer< + '__ctx, #event #type_gen, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> #where_clause + { fn transform<'me, 'out>( &'me self, __event: #event #type_gen, __context: &'__ctx __Ctx, ) -> >:: + TransformerTypes<'__ctx, #event #type_gen, __Ctx>>:: TransformedStream<'out> where 'me: 'out, @@ -451,8 +461,12 @@ impl Definition { syn::WherePredicate, syn::Token![,], > = parse_quote! { + __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: #( + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, #var_type, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, #var_type, __Ctx > @@ -461,7 +475,7 @@ impl Definition { 'static #( + ::std::convert::From<< - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, #var_type, __Ctx > >::Transformed> @@ -470,16 +484,16 @@ impl Definition { 'static #( + ::std::convert::From<< - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, #var_type, __Ctx > >::Error> )*, #( - >::Transformed: 'static, - >::Error: 'static, )* @@ -494,16 +508,15 @@ impl Definition { generics } - /// Generates code of [`Transformer::Transformed`][0] associated type. + /// Generates code of [`TransformerTypes::Transformed`][0] associated type. /// /// This is basically a recursive type /// [`Either`]`>`, where every `VarN` is an - /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a + /// enum variant's [`TransformerTypes::TransformedStream`][0] wrapped in a /// [`stream::Map`] with a function that uses [`From`] impl to transform /// [`Event`]s into compatible ones. /// - /// [0]: arcana_core::es::event::adapter::Transformer::Transformed - /// [1]: arcana_core::es::event::adapter::Transformer::TransformedStream + /// [0]: arcana_core::es::event::adapter::TransformerTypes::Transformed /// [`Either`]: futures::future::Either /// [`Event`]: trait@::arcana_core::es::Event /// [`stream::Map`]: futures::stream::Map @@ -515,23 +528,25 @@ impl Definition { let transformed_stream = |from: &syn::Type| { quote! { ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< >:: + TransformerTypes<'__ctx, #from, __Ctx>>:: Transformed, >::Error, + TransformerTypes<'__ctx, #from, __Ctx>>:: + Error, >, ) -> ::std::result::Result< >:: + TransformerTypes<'__ctx, #event #ty_gen, __Ctx>>:: Transformed, >::Error, + TransformerTypes<'__ctx, #event #ty_gen, __Ctx>>:: + Error, > > } @@ -696,53 +711,61 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter:: + TransformerTypes< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> where + __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent, __Ctx > >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent, __Ctx > >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent, __Ctx > >::Error>, >::Transformed: + TransformerTypes<'__ctx, FileEvent, __Ctx> >::Transformed: 'static, >::Error: + TransformerTypes<'__ctx, FileEvent, __Ctx> >::Error: 'static, >::Transformed: + TransformerTypes<'__ctx, ChatEvent, __Ctx> >::Transformed: 'static, >::Error: + TransformerTypes<'__ctx, ChatEvent, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: @@ -750,65 +773,131 @@ mod spec { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = - ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> + where + Self: 'out + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >:: - Transformed, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Transformed, >:: - Error, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >:: - Transformed, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Transformed, >:: - Error, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Error, > >, >; + } + #[automatically_derived] + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> + where + __Ctx: '__ctx, + __A: ::arcana::es::event::adapter::Returning, + Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + >, + <__A as ::arcana::es::event::adapter::Returning>::Transformed: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + >::Transformed> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + >::Transformed>, + <__A as ::arcana::es::event::adapter::Returning>::Error: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + >::Error>, + >::Transformed: + 'static, + >::Error: + 'static, + >::Transformed: + 'static, + >::Error: + 'static + { fn transform<'me, 'out>( &'me self, __event: Event, __context: &'__ctx __Ctx, ) -> >:: - TransformedStream<'out> + TransformerTypes<'__ctx, Event, __Ctx>>:: + TransformedStream<'out> where 'me: 'out, '__ctx: 'out, @@ -991,50 +1080,60 @@ mod spec { #[automatically_derived] impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: - Transformer<'__ctx, Event<'a, F, C>, __Ctx> for + TransformerTypes<'__ctx, Event<'a, F, C>, __Ctx> for ::arcana::es::event::adapter::Adapted<__A> where + __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent<'a, F>, __Ctx > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent<'a, C>, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent<'a, C>, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent<'a, F>, __Ctx> >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent<'a, C>, __Ctx> >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent<'a, F>, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent<'a, C>, __Ctx > >::Error>, , __Ctx> >::Transformed: - 'static, + TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx> + >::Transformed: 'static, , __Ctx> >::Error: - 'static, + TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx> + >::Error: 'static, , __Ctx> >::Transformed: - 'static, + TransformerTypes< + '__ctx, ChatEvent<'a, C>, __Ctx> + >::Transformed: 'static, , __Ctx> >::Error: + TransformerTypes<'__ctx, ChatEvent<'a, C>, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: @@ -1042,69 +1141,133 @@ mod spec { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = - ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> + where + Self: 'out + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx - >>::TransformedStream<'out>, + , __Ctx + >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx - >>::TransformedStream<'out>, + , __Ctx + >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, > >, >; + } + #[automatically_derived] + impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: + Transformer<'__ctx, Event<'a, F, C>, __Ctx> for + ::arcana::es::event::adapter::Adapted<__A> + where + __Ctx: '__ctx, + __A: ::arcana::es::event::adapter::Returning, + Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, FileEvent<'a, F>, __Ctx + > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent<'a, C>, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent<'a, C>, __Ctx + >, + <__A as ::arcana::es::event::adapter::Returning>::Transformed: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx> + >::Transformed> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent<'a, C>, __Ctx> + >::Transformed>, + <__A as ::arcana::es::event::adapter::Returning>::Error: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent<'a, F>, __Ctx + > + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent<'a, C>, __Ctx + > + >::Error>, + , __Ctx> + >::Transformed: 'static, + , __Ctx> + >::Error: 'static, + , __Ctx> + >::Transformed: 'static, + , __Ctx> >::Error: + 'static + { fn transform<'me, 'out>( &'me self, __event: Event<'a, F, C>, __context: &'__ctx __Ctx, ) -> , __Ctx>>:: - TransformedStream<'out> + TransformerTypes< + '__ctx, Event<'a, F, C>, __Ctx + >>::TransformedStream<'out> where 'me: 'out, '__ctx: 'out, @@ -1289,116 +1452,192 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter:: + TransformerTypes< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> where + __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent, __Ctx > >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent, __Ctx > >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, FileEvent, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::Transformer< + Self as ::arcana::es::event::adapter::TransformerTypes< '__ctx, ChatEvent, __Ctx > >::Error>, >::Transformed: + TransformerTypes<'__ctx, FileEvent, __Ctx> >::Transformed: 'static, >::Error: 'static, + TransformerTypes<'__ctx, FileEvent, __Ctx> >::Error: + 'static, >::Transformed: + TransformerTypes<'__ctx, ChatEvent, __Ctx> >::Transformed: 'static, >::Error: 'static + TransformerTypes<'__ctx, ChatEvent, __Ctx> >::Error: + 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = - ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> + where + Self: 'out + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >:: - Transformed, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Transformed, >:: - Error, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >:: - Transformed, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Transformed, >:: - Error, + TransformerTypes< + '__ctx, Event, __Ctx + >>::Error, > >, >; + } + #[automatically_derived] + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> + where + __Ctx: '__ctx, + __A: ::arcana::es::event::adapter::Returning, + Self: + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, FileEvent, __Ctx + > + + ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + + ::arcana::es::event::adapter::Transformer< + '__ctx, ChatEvent, __Ctx + >, + <__A as ::arcana::es::event::adapter::Returning>::Transformed: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + >::Transformed> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + >::Transformed>, + <__A as ::arcana::es::event::adapter::Returning>::Error: + 'static + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, FileEvent, __Ctx + > + >::Error> + + ::std::convert::From< < + Self as ::arcana::es::event::adapter::TransformerTypes< + '__ctx, ChatEvent, __Ctx + > + >::Error>, + >::Transformed: + 'static, + >::Error: + 'static, + >::Transformed: + 'static, + >::Error: + 'static + { fn transform<'me, 'out>( &'me self, __event: Event, __context: &'__ctx __Ctx, ) -> >:: + TransformerTypes<'__ctx, Event, __Ctx>>:: TransformedStream<'out> where 'me: 'out, diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index 81224d2..f5b835c 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -16,7 +16,9 @@ use ref_cast::RefCast; use crate::spell; #[doc(inline)] -pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; +pub use self::transformer::{ + strategy, Adapt, Strategy, Transformer, Types as TransformerTypes, +}; /// Specifies result of [`Adapter`]. /// @@ -305,20 +307,22 @@ where Ctx: ?Sized + 'ctx, Events: Stream, Adapted: Transformer<'ctx, Events::Item, Context>, - A::Transformed: From< - as Transformer< - 'ctx, - Events::Item, - Context, - >>::Transformed, - >, - A::Error: From< - as Transformer< - 'ctx, - Events::Item, - Context, - >>::Error, - >, + A::Transformed: + From< + as TransformerTypes< + 'ctx, + Events::Item, + Context, + >>::Transformed, + >, + A::Error: + From< + as TransformerTypes< + 'ctx, + Events::Item, + Context, + >>::Error, + >, { type Error = ::Error; type Transformed = ::Transformed; @@ -384,10 +388,12 @@ where /// /// Basically applies [`Transformer::transform()`] to every element of /// [`Adapter`]s `Events` [`Stream`] and flattens it. +#[allow(explicit_outlives_requirements)] // false positive #[pin_project] pub struct TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - Adapter: Transformer<'ctx, Events::Item, Ctx>, + 'ctx: 'out, + Adapter: Transformer<'ctx, Events::Item, Ctx> + 'out, Ctx: ?Sized, Events: Stream, { @@ -427,11 +433,11 @@ where /// Alias for [`TransformedStream::transformed_stream`]. type AdapterTransformedStream<'ctx, 'out, Event, Adapter, Ctx> = future::Either< - >::TransformedStream<'out>, + >::TransformedStream<'out>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; @@ -464,10 +470,11 @@ where Ctx: ?Sized, Adapter: Transformer<'ctx, Events::Item, Ctx> + Returning, Events: Stream, - ::Transformed: - From<>::Transformed>, + ::Transformed: From< + >::Transformed, + >, ::Error: - From<>::Error>, + From<>::Error>, { type Item = Result< ::Transformed, diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index 1211724..8d54239 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -22,25 +22,13 @@ pub trait Adapt { type Strategy; } -/// Facility to convert [`Event`]s. -/// Typical use cases include (but are not limited to): -/// -/// - [`Skip`]ping unused [`Event`]s; -/// - Transforming (ex: from one [`Version`] to another); -/// - [`Split`]ting existing [`Event`]s into more granular ones. +/// Types of [`Transformer`] trait. /// -/// Provided with blanket impl for [`Adapt`] implementors, so usually you -/// shouldn't implement it manually. For more flexibility consider using -/// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. -/// See [`Adapter`] for more info. +/// Exists only because current `GATs` implementation seems to be broken. +/// More detailed explanation: [comment]. /// -/// [`Adapter`]: crate::es::event::Adapter -/// [`Custom`]: strategy::Custom -/// [`Event`]: crate::es::Event -/// [`Skip`]: strategy::Skip -/// [`Split`]: strategy::Split -/// [`Version`]: crate::es::event::Version -pub trait Transformer<'ctx, Event, Ctx: ?Sized> { +/// [comment]: https://github.com/arcana-rs/arcana/pull/4#issuecomment-974068300 +pub trait Types<'ctx, Event, Ctx: ?Sized + 'ctx> { /// Error of this [`Transformer`]. type Error; @@ -55,15 +43,39 @@ pub trait Transformer<'ctx, Event, Ctx: ?Sized> { /// [`Transformed`]: Self::Transformed type TransformedStream<'out>: Stream< Item = Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, - > + 'out; + > + 'out + where + Self: 'out; +} +/// Facility to convert [`Event`]s. +/// Typical use cases include (but are not limited to): +/// +/// - [`Skip`]ping unused [`Event`]s; +/// - Transforming (ex: from one [`Version`] to another); +/// - [`Split`]ting existing [`Event`]s into more granular ones. +/// +/// Provided with blanket impl for [`Adapt`] implementors, so usually you +/// shouldn't implement it manually. For more flexibility consider using +/// [`Custom`] or implementing your own [`Strategy`] in case it will be reused. +/// See [`Adapter`] for more info. +/// +/// [`Adapter`]: crate::es::event::Adapter +/// [`Custom`]: strategy::Custom +/// [`Event`]: crate::es::Event +/// [`Skip`]: strategy::Skip +/// [`Split`]: strategy::Split +/// [`Version`]: crate::es::event::Version +pub trait Transformer<'ctx, Event, Ctx: ?Sized + 'ctx>: + Types<'ctx, Event, Ctx> +{ /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed + /// [`Transformed`]: TransformerTypes::Transformed fn transform<'me, 'out>( &'me self, event: Event, diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index 99ec1ad..da53e79 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -23,8 +23,10 @@ where type Error = Adapter::Error; type Transformed = Event; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = - stream::Once>>; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Once>>; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index 8d41997..bc4c87f 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -43,7 +43,9 @@ pub trait Customize { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + Self: 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -64,7 +66,10 @@ where type Context = >::Context; type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out> = Adapter::TransformedStream<'out>; + type TransformedStream<'out> + where + Adapter: 'out, + = Adapter::TransformedStream<'out>; fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, diff --git a/core/src/es/event/adapter/transformer/strategy/into.rs b/core/src/es/event/adapter/transformer/strategy/into.rs index ecf1c2a..fd72783 100644 --- a/core/src/es/event/adapter/transformer/strategy/into.rs +++ b/core/src/es/event/adapter/transformer/strategy/into.rs @@ -26,7 +26,10 @@ where type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'out> = stream::MapOk< + type TransformedStream<'out> + where + Adapter: 'out, + = stream::MapOk< InnerStrategy::TransformedStream<'out>, IntoFn, >; diff --git a/core/src/es/event/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs index d12f878..c2ea915 100644 --- a/core/src/es/event/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -12,7 +12,7 @@ use futures::Stream; use crate::es::{event, event::adapter}; -use super::{Adapt, Transformer}; +use super::{Adapt, Transformer, Types}; #[doc(inline)] pub use self::{ @@ -53,7 +53,9 @@ pub trait Strategy { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + Adapter: 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -66,7 +68,7 @@ pub trait Strategy { ) -> Self::TransformedStream<'out>; } -impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> +impl<'ctx, Event, Adapter, Ctx> Types<'ctx, Event, Ctx> for adapter::Adapted where Event: event::VersionedOrRaw, @@ -77,17 +79,38 @@ where Adapter::Error: From<>::Error>, Ctx: Borrow<>::Context> - + ?Sized, + + ?Sized + + 'ctx, >::Context: 'ctx, { type Error = >::Error; type Transformed = >::Transformed; - type TransformedStream<'out> = >::TransformedStream<'out>; + type TransformedStream<'out> + where + Self: 'out, + = >::TransformedStream<'out>; +} +impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> + for adapter::Adapted +where + Self: Types<'ctx, Event, Ctx>, + Event: event::VersionedOrRaw, + Adapter: Adapt + adapter::Returning, + Adapter::Strategy: Strategy, + Adapter::Transformed: + From<>::Transformed>, + Adapter::Error: + From<>::Error>, + Ctx: Borrow<>::Context> + + ?Sized + + 'ctx, + >::Context: 'ctx, + for<'o> >::TransformedStream<'o>: From< + >::TransformedStream<'o>, + >, +{ fn transform<'me, 'out>( &'me self, event: Event, @@ -102,5 +125,6 @@ where event, context.borrow(), ) + .into() } } diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index e7c73d7..03d7c70 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -23,8 +23,10 @@ where type Error = Adapter::Error; type Transformed = Adapter::Transformed; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = - stream::Empty>; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Empty>; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index a668545..e560a4e 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -42,7 +42,10 @@ where type Error = Adapter::Error; type Transformed = ::Item; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = SplitStream; + type TransformedStream<'o> + where + Adapter: 'o, + = SplitStream; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/src/es/event/adapter/mod.rs b/src/es/event/adapter/mod.rs index 2323dc4..9fc3125 100644 --- a/src/es/event/adapter/mod.rs +++ b/src/es/event/adapter/mod.rs @@ -3,7 +3,9 @@ pub mod transformer; #[doc(inline)] -pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; +pub use self::transformer::{ + strategy, Adapt, Strategy, Transformer, Types as TransformerTypes, +}; #[doc(inline)] pub use arcana_core::es::event::adapter::{ diff --git a/src/es/event/adapter/transformer/mod.rs b/src/es/event/adapter/transformer/mod.rs index 71d8619..eac1644 100644 --- a/src/es/event/adapter/transformer/mod.rs +++ b/src/es/event/adapter/transformer/mod.rs @@ -6,4 +6,6 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::event::adapter::transformer::{Adapt, Transformer}; +pub use arcana_core::es::event::adapter::transformer::{ + Adapt, Transformer, Types, +}; From 40bd6d723cfb38f0f751ef2845e1cc646fe3d848 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 17:30:58 +0300 Subject: [PATCH 095/104] Make clippy happy --- examples/chat/src/storage/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 89ab89d..654fba6 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -14,6 +14,7 @@ pub enum Event { Email(EmailEvent), } +#[allow(clippy::enum_variant_names)] #[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), From 1654613165a68099de4cb8eeb6e105065cbb44de Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 17:44:25 +0300 Subject: [PATCH 096/104] Docs --- core/src/es/event/adapter/transformer/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index 8d54239..d902468 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -22,6 +22,8 @@ pub trait Adapt { type Strategy; } +// TODO: Merge this trait back into Transformer once issue is resolved: +// https://github.com/rust-lang/rust/issues/91036#issuecomment-974127413 /// Types of [`Transformer`] trait. /// /// Exists only because current `GATs` implementation seems to be broken. @@ -75,7 +77,7 @@ pub trait Transformer<'ctx, Event, Ctx: ?Sized + 'ctx>: /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event - /// [`Transformed`]: TransformerTypes::Transformed + /// [`Transformed`]: Types::Transformed fn transform<'me, 'out>( &'me self, event: Event, From 28b209a1d31736178882b09dc5602d43a2b8cf56 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 19 Nov 2021 17:53:16 +0300 Subject: [PATCH 097/104] Lint --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 164a848..145396c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,3 +92,7 @@ pub mod spell { #[doc(inline)] pub use arcana_core::spell::Borrowed; } + +// To avoid lint errors in case `derive` feature is enabled and `es` isn't. +#[cfg(all(feature = "derive", not(feature = "es")))] +use arcana_codegen as _; From da2c4ec20a3a2703c3fc169df4ce0b313393b894 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 22 Nov 2021 09:08:11 +0300 Subject: [PATCH 098/104] Corrections --- codegen/Cargo.toml | 3 +- codegen/shim/Cargo.toml | 1 - codegen/src/lib.rs | 2 - core/src/es/event/adapter/mod.rs | 10 ++--- core/src/es/event/adapter/transformer/mod.rs | 10 ++--- .../adapter/transformer/strategy/as_is.rs | 1 - .../adapter/transformer/strategy/custom.rs | 4 ++ .../adapter/transformer/strategy/skip.rs | 1 - .../adapter/transformer/strategy/split.rs | 1 - core/src/spell.rs | 4 ++ examples/chat/Cargo.toml | 4 +- examples/chat/src/storage/chat.rs | 37 ++++++++++++++++--- examples/chat/src/storage/mod.rs | 4 -- src/lib.rs | 2 +- 14 files changed, 54 insertions(+), 30 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 24e1f46..6c820be 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -18,7 +18,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -doc = ["arcana-codegen-shim/doc", "arcana-core", "futures"] # only for generating documentation +doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documentation [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } @@ -26,4 +26,3 @@ static_assertions = { version = "1.1", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } -futures = { version = "0.3", default-features = false, optional = true } diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 813dfcb..28b733d 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -32,4 +32,3 @@ arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], o [dev-dependencies] arcana = { path = "../..", features = ["derive", "es"] } -derive_more = { version = "0.99", features = ["from"], default-features = false } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 413991c..a99c57c 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -90,7 +90,5 @@ pub mod es; // Only for generating documentation. #[cfg(feature = "doc")] use arcana_core as _; -#[cfg(feature = "doc")] -use futures as _; pub use static_assertions as sa; diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index f5b835c..1f32061 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -130,9 +130,9 @@ pub trait Returning { /// # } /// ``` /// -/// In case some of your [`Strategies`] are [`Custom`] with non-`()` -/// [`Customize::Context`], provided `context` should be able to be [`Borrow`]ed -/// as `dyn Trait`. +/// In case some of your [`Strategies`] are [`Custom`] or manual impl with +/// non-`()` [`Strategy::Context`], provided `context` should be able to be +/// [`Borrow`]ed as `dyn Trait`. /// /// ```rust /// # #![feature(generic_associated_types)] @@ -256,12 +256,12 @@ pub trait Returning { /// /// [`Borrow`]: std::borrow::Borrow /// [`Custom`]: transformer::strategy::Custom -/// [`Customize::Context`]: transformer::strategy::Customize::Context /// [`Error`]: Self::Error /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split /// [`Strategies`]: Strategy +/// [`Strategy::Context`]: transformer::Strategy::Context /// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version /// [`VersionedEvent`]: crate::es::VersionedEvent @@ -347,7 +347,7 @@ where } } -/// Wrapper around `context` in [`Adapter::transform_all()`] method use in pair +/// Wrapper around `context` in [`Adapter::transform_all()`] method used in pair /// with [`spell::Borrowed`] to hack around orphan rules. Shouldn't be used /// manually. #[derive(Clone, Copy, Debug, RefCast)] diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index d902468..01d1485 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -8,8 +8,8 @@ use futures::Stream; pub use strategy::Strategy; /// To use [`Adapter`] with some [`Event`], you should provide [`Strategy`] -/// for every [`VersionedEvent`] involved with this [`Event`] and implement -/// [`Returning`] on [`Adapter`] itself. +/// for every [`VersionedEvent`] involved with this [`Event`] and use +/// [`Adapter`] derive macro on struct itself. /// /// [`Adapter`]: crate::es::event::Adapter /// [`Event`]: crate::es::Event @@ -23,11 +23,11 @@ pub trait Adapt { } // TODO: Merge this trait back into Transformer once issue is resolved: -// https://github.com/rust-lang/rust/issues/91036#issuecomment-974127413 +// https://github.com/rust-lang/rust/issues/91036 /// Types of [`Transformer`] trait. /// -/// Exists only because current `GATs` implementation seems to be broken. -/// More detailed explanation: [comment]. +/// Exists only because current `GATs` implementation seems to be broken. More +/// detailed explanation: [comment]. /// /// [comment]: https://github.com/arcana-rs/arcana/pull/4#issuecomment-974068300 pub trait Types<'ctx, Event, Ctx: ?Sized + 'ctx> { diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index da53e79..d494e94 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -22,7 +22,6 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Event; - #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index bc4c87f..1143d39 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -5,6 +5,10 @@ use futures::Stream; use super::{event, Strategy}; /// [`Strategy`] for some custom conversion provided by [`Customize`]. +/// +/// This [`Strategy`] should be used in case you don't plan to reuse +/// [`Customize`] impl. Otherwise you should implement [`Strategy`] on your +/// custom struct and reuse it. #[derive(Clone, Copy, Debug)] pub struct Custom; diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index 03d7c70..7079b0a 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -22,7 +22,6 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; - #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index e560a4e..d9a3812 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -41,7 +41,6 @@ where type Context = (); type Error = Adapter::Error; type Transformed = ::Item; - #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/spell.rs b/core/src/spell.rs index a8a333a..06d589e 100644 --- a/core/src/spell.rs +++ b/core/src/spell.rs @@ -4,6 +4,10 @@ use derive_more::Deref; use ref_cast::RefCast; /// Helper to hack around specialization. +/// +/// Used in [`strategy::Customize::Context`][0]. +/// +/// [0]: crate::es::event::adapter::strategy::Customize::Context #[derive(Clone, Copy, Debug, Deref, RefCast)] #[repr(transparent)] pub struct Borrowed(pub T); diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index 58784d9..e2cfe58 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "arcana-example-chat" version = "0.0.0" -edition = "2018" -resolver = "2" +edition = "2021" +rust-version = "1.56" description = "Simple chat app using arcana framework." authors = [ "Ilya Solovyiov ", diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 1552f4d..745c05d 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,7 +1,8 @@ use arcana::es::{ self, - event::adapter::{strategy, Adapt}, + event::adapter::{self, strategy, Adapt, Strategy}, }; +use futures::stream; use crate::event; @@ -26,21 +27,21 @@ impl Adapt for Adapter { } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt> for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } // Chats are private by default. @@ -49,3 +50,29 @@ impl From for event::chat::private::Created { Self } } + +/// Custom [`strategy::Skip`] implementation. +pub struct CustomSkip; + +impl Strategy for CustomSkip +where + Adapter: adapter::Returning, + Adapter::Transformed: 'static, + Adapter::Error: 'static, +{ + type Context = (); + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Empty>; + + fn transform<'me: 'out, 'ctx: 'out, 'out>( + _: &'me Adapter, + _: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> { + stream::empty() + } +} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 654fba6..8284f53 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -51,7 +51,6 @@ mod spec { chat, email, event, message, ChatEvent, EmailEvent, Event, MessageEvent, }; - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn chat_adapter() { let mut chat = Option::::None; @@ -80,7 +79,6 @@ mod spec { ); } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter() { let mut email = Option::::None; @@ -117,7 +115,6 @@ mod spec { ); } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter_with_corrupted_event() { let corrupted_event = @@ -134,7 +131,6 @@ mod spec { assert_eq!(result.unwrap_err().to_string(), "missing field `email`") } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn message_adapter() { let mut message = Option::::None; diff --git a/src/lib.rs b/src/lib.rs index 145396c..9eeb6f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,6 @@ pub mod spell { pub use arcana_core::spell::Borrowed; } -// To avoid lint errors in case `derive` feature is enabled and `es` isn't. +// To avoid clippy errors in case `derive` feature is enabled and `es` isn't. #[cfg(all(feature = "derive", not(feature = "es")))] use arcana_codegen as _; From eda0324be0a10e1cc48116888145d22bb3b3d9df Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 08:49:41 +0300 Subject: [PATCH 099/104] Revert "Corrections" This reverts commit da2c4ec20a3a2703c3fc169df4ce0b313393b894. --- codegen/Cargo.toml | 3 +- codegen/shim/Cargo.toml | 1 + codegen/src/lib.rs | 2 + core/src/es/event/adapter/mod.rs | 10 ++--- core/src/es/event/adapter/transformer/mod.rs | 10 ++--- .../adapter/transformer/strategy/as_is.rs | 1 + .../adapter/transformer/strategy/custom.rs | 4 -- .../adapter/transformer/strategy/skip.rs | 1 + .../adapter/transformer/strategy/split.rs | 1 + core/src/spell.rs | 4 -- examples/chat/Cargo.toml | 4 +- examples/chat/src/storage/chat.rs | 37 +++---------------- examples/chat/src/storage/mod.rs | 4 ++ src/lib.rs | 2 +- 14 files changed, 30 insertions(+), 54 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 6c820be..24e1f46 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -18,7 +18,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documentation +doc = ["arcana-codegen-shim/doc", "arcana-core", "futures"] # only for generating documentation [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } @@ -26,3 +26,4 @@ static_assertions = { version = "1.1", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } +futures = { version = "0.3", default-features = false, optional = true } diff --git a/codegen/shim/Cargo.toml b/codegen/shim/Cargo.toml index 28b733d..813dfcb 100644 --- a/codegen/shim/Cargo.toml +++ b/codegen/shim/Cargo.toml @@ -32,3 +32,4 @@ arcana-core = { version = "0.1.0-dev", path = "../../core", features = ["es"], o [dev-dependencies] arcana = { path = "../..", features = ["derive", "es"] } +derive_more = { version = "0.99", features = ["from"], default-features = false } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index a99c57c..413991c 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -90,5 +90,7 @@ pub mod es; // Only for generating documentation. #[cfg(feature = "doc")] use arcana_core as _; +#[cfg(feature = "doc")] +use futures as _; pub use static_assertions as sa; diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index 1f32061..f5b835c 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -130,9 +130,9 @@ pub trait Returning { /// # } /// ``` /// -/// In case some of your [`Strategies`] are [`Custom`] or manual impl with -/// non-`()` [`Strategy::Context`], provided `context` should be able to be -/// [`Borrow`]ed as `dyn Trait`. +/// In case some of your [`Strategies`] are [`Custom`] with non-`()` +/// [`Customize::Context`], provided `context` should be able to be [`Borrow`]ed +/// as `dyn Trait`. /// /// ```rust /// # #![feature(generic_associated_types)] @@ -256,12 +256,12 @@ pub trait Returning { /// /// [`Borrow`]: std::borrow::Borrow /// [`Custom`]: transformer::strategy::Custom +/// [`Customize::Context`]: transformer::strategy::Customize::Context /// [`Error`]: Self::Error /// [`Event`]: crate::es::Event /// [`Skip`]: transformer::strategy::Skip /// [`Split`]: transformer::strategy::Split /// [`Strategies`]: Strategy -/// [`Strategy::Context`]: transformer::Strategy::Context /// [`Transformed`]: Self::Transformed /// [`Version`]: crate::es::event::Version /// [`VersionedEvent`]: crate::es::VersionedEvent @@ -347,7 +347,7 @@ where } } -/// Wrapper around `context` in [`Adapter::transform_all()`] method used in pair +/// Wrapper around `context` in [`Adapter::transform_all()`] method use in pair /// with [`spell::Borrowed`] to hack around orphan rules. Shouldn't be used /// manually. #[derive(Clone, Copy, Debug, RefCast)] diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index 01d1485..d902468 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -8,8 +8,8 @@ use futures::Stream; pub use strategy::Strategy; /// To use [`Adapter`] with some [`Event`], you should provide [`Strategy`] -/// for every [`VersionedEvent`] involved with this [`Event`] and use -/// [`Adapter`] derive macro on struct itself. +/// for every [`VersionedEvent`] involved with this [`Event`] and implement +/// [`Returning`] on [`Adapter`] itself. /// /// [`Adapter`]: crate::es::event::Adapter /// [`Event`]: crate::es::Event @@ -23,11 +23,11 @@ pub trait Adapt { } // TODO: Merge this trait back into Transformer once issue is resolved: -// https://github.com/rust-lang/rust/issues/91036 +// https://github.com/rust-lang/rust/issues/91036#issuecomment-974127413 /// Types of [`Transformer`] trait. /// -/// Exists only because current `GATs` implementation seems to be broken. More -/// detailed explanation: [comment]. +/// Exists only because current `GATs` implementation seems to be broken. +/// More detailed explanation: [comment]. /// /// [comment]: https://github.com/arcana-rs/arcana/pull/4#issuecomment-974068300 pub trait Types<'ctx, Event, Ctx: ?Sized + 'ctx> { diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index d494e94..da53e79 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -22,6 +22,7 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Event; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index 1143d39..bc4c87f 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -5,10 +5,6 @@ use futures::Stream; use super::{event, Strategy}; /// [`Strategy`] for some custom conversion provided by [`Customize`]. -/// -/// This [`Strategy`] should be used in case you don't plan to reuse -/// [`Customize`] impl. Otherwise you should implement [`Strategy`] on your -/// custom struct and reuse it. #[derive(Clone, Copy, Debug)] pub struct Custom; diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index 7079b0a..03d7c70 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -22,6 +22,7 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index d9a3812..e560a4e 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -41,6 +41,7 @@ where type Context = (); type Error = Adapter::Error; type Transformed = ::Item; + #[allow(unused_lifetimes)] // false positive type TransformedStream<'o> where Adapter: 'o, diff --git a/core/src/spell.rs b/core/src/spell.rs index 06d589e..a8a333a 100644 --- a/core/src/spell.rs +++ b/core/src/spell.rs @@ -4,10 +4,6 @@ use derive_more::Deref; use ref_cast::RefCast; /// Helper to hack around specialization. -/// -/// Used in [`strategy::Customize::Context`][0]. -/// -/// [0]: crate::es::event::adapter::strategy::Customize::Context #[derive(Clone, Copy, Debug, Deref, RefCast)] #[repr(transparent)] pub struct Borrowed(pub T); diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index e2cfe58..58784d9 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "arcana-example-chat" version = "0.0.0" -edition = "2021" -rust-version = "1.56" +edition = "2018" +resolver = "2" description = "Simple chat app using arcana framework." authors = [ "Ilya Solovyiov ", diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 745c05d..1552f4d 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,8 +1,7 @@ use arcana::es::{ self, - event::adapter::{self, strategy, Adapt, Strategy}, + event::adapter::{strategy, Adapt}, }; -use futures::stream; use crate::event; @@ -27,21 +26,21 @@ impl Adapt for Adapter { } impl Adapt for Adapter { - type Strategy = CustomSkip; + type Strategy = strategy::Skip; } impl Adapt for Adapter { - type Strategy = CustomSkip; + type Strategy = strategy::Skip; } impl Adapt for Adapter { - type Strategy = CustomSkip; + type Strategy = strategy::Skip; } impl Adapt> for Adapter { - type Strategy = CustomSkip; + type Strategy = strategy::Skip; } // Chats are private by default. @@ -50,29 +49,3 @@ impl From for event::chat::private::Created { Self } } - -/// Custom [`strategy::Skip`] implementation. -pub struct CustomSkip; - -impl Strategy for CustomSkip -where - Adapter: adapter::Returning, - Adapter::Transformed: 'static, - Adapter::Error: 'static, -{ - type Context = (); - type Error = Adapter::Error; - type Transformed = Adapter::Transformed; - type TransformedStream<'o> - where - Adapter: 'o, - = stream::Empty>; - - fn transform<'me: 'out, 'ctx: 'out, 'out>( - _: &'me Adapter, - _: Event, - _: &'ctx Self::Context, - ) -> Self::TransformedStream<'out> { - stream::empty() - } -} diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 8284f53..654fba6 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -51,6 +51,7 @@ mod spec { chat, email, event, message, ChatEvent, EmailEvent, Event, MessageEvent, }; + #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn chat_adapter() { let mut chat = Option::::None; @@ -79,6 +80,7 @@ mod spec { ); } + #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter() { let mut email = Option::::None; @@ -115,6 +117,7 @@ mod spec { ); } + #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter_with_corrupted_event() { let corrupted_event = @@ -131,6 +134,7 @@ mod spec { assert_eq!(result.unwrap_err().to_string(), "missing field `email`") } + #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn message_adapter() { let mut message = Option::::None; diff --git a/src/lib.rs b/src/lib.rs index 9eeb6f6..145396c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,6 @@ pub mod spell { pub use arcana_core::spell::Borrowed; } -// To avoid clippy errors in case `derive` feature is enabled and `es` isn't. +// To avoid lint errors in case `derive` feature is enabled and `es` isn't. #[cfg(all(feature = "derive", not(feature = "es")))] use arcana_codegen as _; From c065747422c652cdfeb78ea1e9310bf055273241 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 08:49:41 +0300 Subject: [PATCH 100/104] Revert "Lint" This reverts commit 28b209a1d31736178882b09dc5602d43a2b8cf56. --- src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 145396c..164a848 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,3 @@ pub mod spell { #[doc(inline)] pub use arcana_core::spell::Borrowed; } - -// To avoid lint errors in case `derive` feature is enabled and `es` isn't. -#[cfg(all(feature = "derive", not(feature = "es")))] -use arcana_codegen as _; From 0762876d6277e7f3d3dfed9fa81d03cbcdbcd557 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 08:49:41 +0300 Subject: [PATCH 101/104] Revert "Docs" This reverts commit 1654613165a68099de4cb8eeb6e105065cbb44de. --- core/src/es/event/adapter/transformer/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index d902468..8d54239 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -22,8 +22,6 @@ pub trait Adapt { type Strategy; } -// TODO: Merge this trait back into Transformer once issue is resolved: -// https://github.com/rust-lang/rust/issues/91036#issuecomment-974127413 /// Types of [`Transformer`] trait. /// /// Exists only because current `GATs` implementation seems to be broken. @@ -77,7 +75,7 @@ pub trait Transformer<'ctx, Event, Ctx: ?Sized + 'ctx>: /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event - /// [`Transformed`]: Types::Transformed + /// [`Transformed`]: TransformerTypes::Transformed fn transform<'me, 'out>( &'me self, event: Event, From e54493648a498598a12b138984349c9711cad327 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 08:49:41 +0300 Subject: [PATCH 102/104] Revert "Make clippy happy" This reverts commit 40bd6d723cfb38f0f751ef2845e1cc646fe3d848. --- examples/chat/src/storage/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 654fba6..89ab89d 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -14,7 +14,6 @@ pub enum Event { Email(EmailEvent), } -#[allow(clippy::enum_variant_names)] #[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), From c080b6c9bef8853aa139f0b0c92d9ad01dd0763e Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 08:49:41 +0300 Subject: [PATCH 103/104] Revert "Dance around new GAT rules" This reverts commit 42889ebb7ac0918d8ef84aee2d623d99a3ae0fc8. --- codegen/impl/src/es/event/mod.rs | 461 +++++------------- core/src/es/event/adapter/mod.rs | 51 +- core/src/es/event/adapter/transformer/mod.rs | 54 +- .../adapter/transformer/strategy/as_is.rs | 6 +- .../adapter/transformer/strategy/custom.rs | 9 +- .../adapter/transformer/strategy/into.rs | 5 +- .../event/adapter/transformer/strategy/mod.rs | 40 +- .../adapter/transformer/strategy/skip.rs | 6 +- .../adapter/transformer/strategy/split.rs | 5 +- src/es/event/adapter/mod.rs | 4 +- src/es/event/adapter/transformer/mod.rs | 4 +- 11 files changed, 172 insertions(+), 473 deletions(-) diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index 73703f7..fb2ee71 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -395,7 +395,7 @@ impl Definition { quote! { #[automatically_derived] - impl #impl_gen ::arcana::es::event::adapter::TransformerTypes< + impl #impl_gen ::arcana::es::event::adapter::Transformer< '__ctx, #event #type_gen, __Ctx > for ::arcana::es::event::adapter::Adapted<__A> #where_clause { @@ -404,24 +404,14 @@ impl Definition { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> - where - Self: 'out - = #transformed; - } - + type TransformedStream<'out> = #transformed; - #[automatically_derived] - impl #impl_gen ::arcana::es::event::adapter::Transformer< - '__ctx, #event #type_gen, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> #where_clause - { fn transform<'me, 'out>( &'me self, __event: #event #type_gen, __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, #event #type_gen, __Ctx>>:: TransformedStream<'out> where 'me: 'out, @@ -461,12 +451,8 @@ impl Definition { syn::WherePredicate, syn::Token![,], > = parse_quote! { - __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: #( - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, #var_type, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, #var_type, __Ctx > @@ -475,7 +461,7 @@ impl Definition { 'static #( + ::std::convert::From<< - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, #var_type, __Ctx > >::Transformed> @@ -484,16 +470,16 @@ impl Definition { 'static #( + ::std::convert::From<< - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, #var_type, __Ctx > >::Error> )*, #( - >::Transformed: 'static, - >::Error: 'static, )* @@ -508,15 +494,16 @@ impl Definition { generics } - /// Generates code of [`TransformerTypes::Transformed`][0] associated type. + /// Generates code of [`Transformer::Transformed`][0] associated type. /// /// This is basically a recursive type /// [`Either`]`>`, where every `VarN` is an - /// enum variant's [`TransformerTypes::TransformedStream`][0] wrapped in a + /// enum variant's [`Transformer::TransformedStream`][1] wrapped in a /// [`stream::Map`] with a function that uses [`From`] impl to transform /// [`Event`]s into compatible ones. /// - /// [0]: arcana_core::es::event::adapter::TransformerTypes::Transformed + /// [0]: arcana_core::es::event::adapter::Transformer::Transformed + /// [1]: arcana_core::es::event::adapter::Transformer::TransformedStream /// [`Either`]: futures::future::Either /// [`Event`]: trait@::arcana_core::es::Event /// [`stream::Map`]: futures::stream::Map @@ -528,25 +515,23 @@ impl Definition { let transformed_stream = |from: &syn::Type| { quote! { ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, fn( ::std::result::Result< >:: + Transformer<'__ctx, #from, __Ctx>>:: Transformed, >:: - Error, + Transformer<'__ctx, #from, __Ctx>>::Error, >, ) -> ::std::result::Result< >:: + Transformer<'__ctx, #event #ty_gen, __Ctx>>:: Transformed, >:: - Error, + Transformer<'__ctx, #event #ty_gen, __Ctx>>::Error, > > } @@ -711,61 +696,53 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter:: - TransformerTypes< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> where - __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx > >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx > >::Error>, >::Transformed: + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed: 'static, >::Error: + Transformer<'__ctx, FileEvent, __Ctx> >::Error: 'static, >::Transformed: + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed: 'static, >::Error: + Transformer<'__ctx, ChatEvent, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: @@ -773,131 +750,65 @@ mod spec { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> - where - Self: 'out - = ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> = + ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, Event, __Ctx>>:: + Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, Event, __Ctx>>:: + Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, >; - } - #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> - where - __Ctx: '__ctx, - __A: ::arcana::es::event::adapter::Returning, - Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, FileEvent, __Ctx - > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, ChatEvent, __Ctx - >, - <__A as ::arcana::es::event::adapter::Returning>::Transformed: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > - >::Transformed> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > - >::Transformed>, - <__A as ::arcana::es::event::adapter::Returning>::Error: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > - >::Error> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > - >::Error>, - >::Transformed: - 'static, - >::Error: - 'static, - >::Transformed: - 'static, - >::Error: - 'static - { fn transform<'me, 'out>( &'me self, __event: Event, __context: &'__ctx __Ctx, ) -> >:: - TransformedStream<'out> + Transformer<'__ctx, Event, __Ctx>>:: + TransformedStream<'out> where 'me: 'out, '__ctx: 'out, @@ -1080,60 +991,50 @@ mod spec { #[automatically_derived] impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: - TransformerTypes<'__ctx, Event<'a, F, C>, __Ctx> for + Transformer<'__ctx, Event<'a, F, C>, __Ctx> for ::arcana::es::event::adapter::Adapted<__A> where - __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent<'a, F>, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent<'a, F>, __Ctx > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent<'a, C>, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent<'a, C>, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent<'a, F>, __Ctx> >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent<'a, C>, __Ctx> >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent<'a, F>, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent<'a, C>, __Ctx > >::Error>, , __Ctx> - >::Transformed: 'static, + Transformer<'__ctx, FileEvent<'a, F>, __Ctx> >::Transformed: + 'static, , __Ctx> - >::Error: 'static, + Transformer<'__ctx, FileEvent<'a, F>, __Ctx> >::Error: + 'static, , __Ctx> - >::Transformed: 'static, + Transformer<'__ctx, ChatEvent<'a, C>, __Ctx> >::Transformed: + 'static, , __Ctx> >::Error: + Transformer<'__ctx, ChatEvent<'a, C>, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: @@ -1141,133 +1042,69 @@ mod spec { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> - where - Self: 'out - = ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> = + ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx - >>::TransformedStream<'out>, + , __Ctx + >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - , __Ctx - >>::TransformedStream<'out>, + , __Ctx + >>::TransformedStream<'out>, fn( ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, >, ) -> ::std::result::Result< , __Ctx >>::Transformed, , __Ctx >>::Error, > >, >; - } - #[automatically_derived] - impl<'a, '__ctx, F, C, __A, __Ctx> ::arcana::es::event::adapter:: - Transformer<'__ctx, Event<'a, F, C>, __Ctx> for - ::arcana::es::event::adapter::Adapted<__A> - where - __Ctx: '__ctx, - __A: ::arcana::es::event::adapter::Returning, - Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent<'a, F>, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, FileEvent<'a, F>, __Ctx - > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent<'a, C>, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, ChatEvent<'a, C>, __Ctx - >, - <__A as ::arcana::es::event::adapter::Returning>::Transformed: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent<'a, F>, __Ctx> - >::Transformed> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent<'a, C>, __Ctx> - >::Transformed>, - <__A as ::arcana::es::event::adapter::Returning>::Error: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent<'a, F>, __Ctx - > - >::Error> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent<'a, C>, __Ctx - > - >::Error>, - , __Ctx> - >::Transformed: 'static, - , __Ctx> - >::Error: 'static, - , __Ctx> - >::Transformed: 'static, - , __Ctx> >::Error: - 'static - { fn transform<'me, 'out>( &'me self, __event: Event<'a, F, C>, __context: &'__ctx __Ctx, ) -> , __Ctx - >>::TransformedStream<'out> + Transformer<'__ctx, Event<'a, F, C>, __Ctx>>:: + TransformedStream<'out> where 'me: 'out, '__ctx: 'out, @@ -1452,192 +1289,116 @@ mod spec { } #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter:: - TransformerTypes< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> + impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< + '__ctx, Event, __Ctx + > for ::arcana::es::event::adapter::Adapted<__A> where - __Ctx: '__ctx, __A: ::arcana::es::event::adapter::Returning, Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > + ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx >, <__A as ::arcana::es::event::adapter::Returning>::Transformed: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > >::Transformed> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx > >::Transformed>, <__A as ::arcana::es::event::adapter::Returning>::Error: 'static + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, FileEvent, __Ctx > >::Error> + ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< + Self as ::arcana::es::event::adapter::Transformer< '__ctx, ChatEvent, __Ctx > >::Error>, >::Transformed: + Transformer<'__ctx, FileEvent, __Ctx> >::Transformed: 'static, >::Error: - 'static, + Transformer<'__ctx, FileEvent, __Ctx> >::Error: 'static, >::Transformed: + Transformer<'__ctx, ChatEvent, __Ctx> >::Transformed: 'static, >::Error: - 'static + Transformer<'__ctx, ChatEvent, __Ctx> >::Error: 'static { type Error = <__A as ::arcana::es::event::adapter::Returning>:: Error; type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> - where - Self: 'out - = ::arcana::es::event::codegen::futures::future::Either< + type TransformedStream<'out> = + ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, Event, __Ctx>>:: + Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, ::arcana::es::event::codegen::futures::stream::Map< - >::TransformedStream<'out>, + >::TransformedStream<'out>, fn( ::std::result::Result< >::Transformed, >::Error, >, ) -> ::std::result::Result< >::Transformed, + Transformer<'__ctx, Event, __Ctx>>:: + Transformed, >::Error, + Transformer<'__ctx, Event, __Ctx>>:: + Error, > >, >; - } - #[automatically_derived] - impl<'__ctx, __A, __Ctx> ::arcana::es::event::adapter::Transformer< - '__ctx, Event, __Ctx - > for ::arcana::es::event::adapter::Adapted<__A> - where - __Ctx: '__ctx, - __A: ::arcana::es::event::adapter::Returning, - Self: - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, FileEvent, __Ctx - > + - ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > + - ::arcana::es::event::adapter::Transformer< - '__ctx, ChatEvent, __Ctx - >, - <__A as ::arcana::es::event::adapter::Returning>::Transformed: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > - >::Transformed> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > - >::Transformed>, - <__A as ::arcana::es::event::adapter::Returning>::Error: - 'static + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, FileEvent, __Ctx - > - >::Error> + - ::std::convert::From< < - Self as ::arcana::es::event::adapter::TransformerTypes< - '__ctx, ChatEvent, __Ctx - > - >::Error>, - >::Transformed: - 'static, - >::Error: - 'static, - >::Transformed: - 'static, - >::Error: - 'static - { fn transform<'me, 'out>( &'me self, __event: Event, __context: &'__ctx __Ctx, ) -> >:: + Transformer<'__ctx, Event, __Ctx>>:: TransformedStream<'out> where 'me: 'out, diff --git a/core/src/es/event/adapter/mod.rs b/core/src/es/event/adapter/mod.rs index f5b835c..81224d2 100644 --- a/core/src/es/event/adapter/mod.rs +++ b/core/src/es/event/adapter/mod.rs @@ -16,9 +16,7 @@ use ref_cast::RefCast; use crate::spell; #[doc(inline)] -pub use self::transformer::{ - strategy, Adapt, Strategy, Transformer, Types as TransformerTypes, -}; +pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; /// Specifies result of [`Adapter`]. /// @@ -307,22 +305,20 @@ where Ctx: ?Sized + 'ctx, Events: Stream, Adapted: Transformer<'ctx, Events::Item, Context>, - A::Transformed: - From< - as TransformerTypes< - 'ctx, - Events::Item, - Context, - >>::Transformed, - >, - A::Error: - From< - as TransformerTypes< - 'ctx, - Events::Item, - Context, - >>::Error, - >, + A::Transformed: From< + as Transformer< + 'ctx, + Events::Item, + Context, + >>::Transformed, + >, + A::Error: From< + as Transformer< + 'ctx, + Events::Item, + Context, + >>::Error, + >, { type Error = ::Error; type Transformed = ::Transformed; @@ -388,12 +384,10 @@ where /// /// Basically applies [`Transformer::transform()`] to every element of /// [`Adapter`]s `Events` [`Stream`] and flattens it. -#[allow(explicit_outlives_requirements)] // false positive #[pin_project] pub struct TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - 'ctx: 'out, - Adapter: Transformer<'ctx, Events::Item, Ctx> + 'out, + Adapter: Transformer<'ctx, Events::Item, Ctx>, Ctx: ?Sized, Events: Stream, { @@ -433,11 +427,11 @@ where /// Alias for [`TransformedStream::transformed_stream`]. type AdapterTransformedStream<'ctx, 'out, Event, Adapter, Ctx> = future::Either< - >::TransformedStream<'out>, + >::TransformedStream<'out>, stream::Empty< Result< - >::Transformed, - >::Error, + >::Transformed, + >::Error, >, >, >; @@ -470,11 +464,10 @@ where Ctx: ?Sized, Adapter: Transformer<'ctx, Events::Item, Ctx> + Returning, Events: Stream, - ::Transformed: From< - >::Transformed, - >, + ::Transformed: + From<>::Transformed>, ::Error: - From<>::Error>, + From<>::Error>, { type Item = Result< ::Transformed, diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index 8d54239..1211724 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -22,35 +22,6 @@ pub trait Adapt { type Strategy; } -/// Types of [`Transformer`] trait. -/// -/// Exists only because current `GATs` implementation seems to be broken. -/// More detailed explanation: [comment]. -/// -/// [comment]: https://github.com/arcana-rs/arcana/pull/4#issuecomment-974068300 -pub trait Types<'ctx, Event, Ctx: ?Sized + 'ctx> { - /// Error of this [`Transformer`]. - type Error; - - /// Converted [`Event`]. - /// - /// [`Event`]: crate::es::Event - type Transformed; - - /// [`Stream`] of [`Transformed`] [`Event`]s. - /// - /// [`Event`]: crate::es::Event - /// [`Transformed`]: Self::Transformed - type TransformedStream<'out>: Stream< - Item = Result< - >::Transformed, - >::Error, - >, - > + 'out - where - Self: 'out; -} - /// Facility to convert [`Event`]s. /// Typical use cases include (but are not limited to): /// @@ -69,13 +40,30 @@ pub trait Types<'ctx, Event, Ctx: ?Sized + 'ctx> { /// [`Skip`]: strategy::Skip /// [`Split`]: strategy::Split /// [`Version`]: crate::es::event::Version -pub trait Transformer<'ctx, Event, Ctx: ?Sized + 'ctx>: - Types<'ctx, Event, Ctx> -{ +pub trait Transformer<'ctx, Event, Ctx: ?Sized> { + /// Error of this [`Transformer`]. + type Error; + + /// Converted [`Event`]. + /// + /// [`Event`]: crate::es::Event + type Transformed; + + /// [`Stream`] of [`Transformed`] [`Event`]s. + /// + /// [`Event`]: crate::es::Event + /// [`Transformed`]: Self::Transformed + type TransformedStream<'out>: Stream< + Item = Result< + >::Transformed, + >::Error, + >, + > + 'out; + /// Converts incoming [`Event`] into [`Transformed`]. /// /// [`Event`]: crate::es::Event - /// [`Transformed`]: TransformerTypes::Transformed + /// [`Transformed`]: Self::Transformed fn transform<'me, 'out>( &'me self, event: Event, diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index da53e79..99ec1ad 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -23,10 +23,8 @@ where type Error = Adapter::Error; type Transformed = Event; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> - where - Adapter: 'o, - = stream::Once>>; + type TransformedStream<'o> = + stream::Once>>; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index bc4c87f..8d41997 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -43,9 +43,7 @@ pub trait Customize { >::Transformed, >::Error, >, - > + 'out - where - Self: 'out; + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -66,10 +64,7 @@ where type Context = >::Context; type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out> - where - Adapter: 'out, - = Adapter::TransformedStream<'out>; + type TransformedStream<'out> = Adapter::TransformedStream<'out>; fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, diff --git a/core/src/es/event/adapter/transformer/strategy/into.rs b/core/src/es/event/adapter/transformer/strategy/into.rs index fd72783..ecf1c2a 100644 --- a/core/src/es/event/adapter/transformer/strategy/into.rs +++ b/core/src/es/event/adapter/transformer/strategy/into.rs @@ -26,10 +26,7 @@ where type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'out> - where - Adapter: 'out, - = stream::MapOk< + type TransformedStream<'out> = stream::MapOk< InnerStrategy::TransformedStream<'out>, IntoFn, >; diff --git a/core/src/es/event/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs index c2ea915..d12f878 100644 --- a/core/src/es/event/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -12,7 +12,7 @@ use futures::Stream; use crate::es::{event, event::adapter}; -use super::{Adapt, Transformer, Types}; +use super::{Adapt, Transformer}; #[doc(inline)] pub use self::{ @@ -53,9 +53,7 @@ pub trait Strategy { >::Transformed, >::Error, >, - > + 'out - where - Adapter: 'out; + > + 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -68,7 +66,7 @@ pub trait Strategy { ) -> Self::TransformedStream<'out>; } -impl<'ctx, Event, Adapter, Ctx> Types<'ctx, Event, Ctx> +impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> for adapter::Adapted where Event: event::VersionedOrRaw, @@ -79,38 +77,17 @@ where Adapter::Error: From<>::Error>, Ctx: Borrow<>::Context> - + ?Sized - + 'ctx, + + ?Sized, >::Context: 'ctx, { type Error = >::Error; type Transformed = >::Transformed; - type TransformedStream<'out> - where - Self: 'out, - = >::TransformedStream<'out>; -} + type TransformedStream<'out> = >::TransformedStream<'out>; -impl<'ctx, Event, Adapter, Ctx> Transformer<'ctx, Event, Ctx> - for adapter::Adapted -where - Self: Types<'ctx, Event, Ctx>, - Event: event::VersionedOrRaw, - Adapter: Adapt + adapter::Returning, - Adapter::Strategy: Strategy, - Adapter::Transformed: - From<>::Transformed>, - Adapter::Error: - From<>::Error>, - Ctx: Borrow<>::Context> - + ?Sized - + 'ctx, - >::Context: 'ctx, - for<'o> >::TransformedStream<'o>: From< - >::TransformedStream<'o>, - >, -{ fn transform<'me, 'out>( &'me self, event: Event, @@ -125,6 +102,5 @@ where event, context.borrow(), ) - .into() } } diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index 03d7c70..e7c73d7 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -23,10 +23,8 @@ where type Error = Adapter::Error; type Transformed = Adapter::Transformed; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> - where - Adapter: 'o, - = stream::Empty>; + type TransformedStream<'o> = + stream::Empty>; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index e560a4e..a668545 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -42,10 +42,7 @@ where type Error = Adapter::Error; type Transformed = ::Item; #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> - where - Adapter: 'o, - = SplitStream; + type TransformedStream<'o> = SplitStream; #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( diff --git a/src/es/event/adapter/mod.rs b/src/es/event/adapter/mod.rs index 9fc3125..2323dc4 100644 --- a/src/es/event/adapter/mod.rs +++ b/src/es/event/adapter/mod.rs @@ -3,9 +3,7 @@ pub mod transformer; #[doc(inline)] -pub use self::transformer::{ - strategy, Adapt, Strategy, Transformer, Types as TransformerTypes, -}; +pub use self::transformer::{strategy, Adapt, Strategy, Transformer}; #[doc(inline)] pub use arcana_core::es::event::adapter::{ diff --git a/src/es/event/adapter/transformer/mod.rs b/src/es/event/adapter/transformer/mod.rs index eac1644..71d8619 100644 --- a/src/es/event/adapter/transformer/mod.rs +++ b/src/es/event/adapter/transformer/mod.rs @@ -6,6 +6,4 @@ pub mod strategy; pub use self::strategy::Strategy; #[doc(inline)] -pub use arcana_core::es::event::adapter::transformer::{ - Adapt, Transformer, Types, -}; +pub use arcana_core::es::event::adapter::transformer::{Adapt, Transformer}; From 5b69ff26629f884694d8431a2f92d19af944b5f4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 15 Dec 2021 09:00:07 +0300 Subject: [PATCH 104/104] Corrections --- codegen/Cargo.toml | 3 +- codegen/impl/src/es/event/mod.rs | 28 +++++++++-- codegen/shim/Cargo.toml | 1 - codegen/src/lib.rs | 2 - core/src/es/event/adapter/mod.rs | 32 ++++++++----- core/src/es/event/adapter/transformer/mod.rs | 10 ++-- .../adapter/transformer/strategy/as_is.rs | 12 ++--- .../adapter/transformer/strategy/custom.rs | 13 +++++- .../adapter/transformer/strategy/into.rs | 5 +- .../event/adapter/transformer/strategy/mod.rs | 14 ++++-- .../adapter/transformer/strategy/skip.rs | 12 ++--- .../adapter/transformer/strategy/split.rs | 11 +++-- core/src/spell.rs | 4 ++ examples/chat/Cargo.toml | 4 +- examples/chat/src/storage/chat.rs | 37 +++++++++++++-- examples/chat/src/storage/email.rs | 16 +++---- examples/chat/src/storage/mod.rs | 46 +++++++++---------- src/lib.rs | 4 ++ 18 files changed, 167 insertions(+), 87 deletions(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 24e1f46..6c820be 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -18,7 +18,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -doc = ["arcana-codegen-shim/doc", "arcana-core", "futures"] # only for generating documentation +doc = ["arcana-codegen-shim/doc", "arcana-core"] # only for generating documentation [dependencies] arcana-codegen-shim = { version = "0.1.0-dev", path = "./shim" } @@ -26,4 +26,3 @@ static_assertions = { version = "1.1", default-features = false } # `doc` feature arcana-core = { version = "0.1.0-dev", path = "../core", features = ["es"], optional = true } -futures = { version = "0.3", default-features = false, optional = true } diff --git a/codegen/impl/src/es/event/mod.rs b/codegen/impl/src/es/event/mod.rs index fb2ee71..893b705 100644 --- a/codegen/impl/src/es/event/mod.rs +++ b/codegen/impl/src/es/event/mod.rs @@ -404,7 +404,12 @@ impl Definition { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = #transformed; + type TransformedStream<'out> + where + '__ctx: 'out, + __A: 'out, + __Ctx: '__ctx + 'out, + = #transformed; fn transform<'me, 'out>( &'me self, @@ -750,7 +755,12 @@ mod spec { type Transformed = <__A as ::arcana::es::event::adapter::Returning>:: Transformed; - type TransformedStream<'out> = + type TransformedStream<'out> + where + '__ctx: 'out, + __A: 'out, + __Ctx: '__ctx + 'out, + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< :: Transformed; - type TransformedStream<'out> = + type TransformedStream<'out> + where + '__ctx: 'out, + __A: 'out, + __Ctx: '__ctx + 'out, + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< :: Transformed; - type TransformedStream<'out> = + type TransformedStream<'out> + where + '__ctx: 'out, + __A: 'out, + __Ctx: '__ctx + 'out, + = ::arcana::es::event::codegen::futures::future::Either< ::arcana::es::event::codegen::futures::stream::Map< { /// /// [`Event`]: crate::es::Event /// [`Transformed`]: Self::Transformed - fn transform_all<'me: 'out, 'out>( + fn transform_all<'me, 'out>( &'me self, events: Events, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out>; + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + Events: 'out; } impl<'ctx, A, Events, Ctx> Adapter<'ctx, Events, Ctx> for A @@ -330,11 +333,16 @@ where Self: 'out, = TransformedStream<'ctx, 'out, Adapted, Events, Context>; - fn transform_all<'me: 'out, 'out>( + fn transform_all<'me, 'out>( &'me self, events: Events, context: &'ctx Ctx, - ) -> Self::TransformedStream<'out> { + ) -> Self::TransformedStream<'out> + where + 'me: 'out, + 'ctx: 'out, + Events: 'out, + { TransformedStream::new( RefCast::ref_cast(self), events, @@ -343,7 +351,7 @@ where } } -/// Wrapper around `context` in [`Adapter::transform_all()`] method use in pair +/// Wrapper around `context` in [`Adapter::transform_all()`] method used in pair /// with [`spell::Borrowed`] to hack around orphan rules. Shouldn't be used /// manually. #[derive(Clone, Copy, Debug, RefCast)] @@ -384,11 +392,13 @@ where /// /// Basically applies [`Transformer::transform()`] to every element of /// [`Adapter`]s `Events` [`Stream`] and flattens it. +#[allow(explicit_outlives_requirements)] // false positive #[pin_project] pub struct TransformedStream<'ctx, 'out, Adapter, Events, Ctx> where - Adapter: Transformer<'ctx, Events::Item, Ctx>, - Ctx: ?Sized, + 'ctx: 'out, + Adapter: Transformer<'ctx, Events::Item, Ctx> + 'out, + Ctx: ?Sized + 'ctx, Events: Stream, { /// [`Stream`] of [`Event`]s to [`Transformer::transform()`]. diff --git a/core/src/es/event/adapter/transformer/mod.rs b/core/src/es/event/adapter/transformer/mod.rs index 1211724..d3671c1 100644 --- a/core/src/es/event/adapter/transformer/mod.rs +++ b/core/src/es/event/adapter/transformer/mod.rs @@ -8,8 +8,8 @@ use futures::Stream; pub use strategy::Strategy; /// To use [`Adapter`] with some [`Event`], you should provide [`Strategy`] -/// for every [`VersionedEvent`] involved with this [`Event`] and implement -/// [`Returning`] on [`Adapter`] itself. +/// for every [`VersionedEvent`] involved with this [`Event`] and use +/// [`Adapter`] derive macro on struct itself. /// /// [`Adapter`]: crate::es::event::Adapter /// [`Event`]: crate::es::Event @@ -58,7 +58,11 @@ pub trait Transformer<'ctx, Event, Ctx: ?Sized> { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + 'ctx: 'out, + Ctx: 'ctx + 'out, + Self: 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// diff --git a/core/src/es/event/adapter/transformer/strategy/as_is.rs b/core/src/es/event/adapter/transformer/strategy/as_is.rs index 99ec1ad..05daf03 100644 --- a/core/src/es/event/adapter/transformer/strategy/as_is.rs +++ b/core/src/es/event/adapter/transformer/strategy/as_is.rs @@ -22,15 +22,15 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Event; - #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = - stream::Once>>; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Once>>; - #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( - _: &Adapter, + _: &'me Adapter, event: Event, - _: &Self::Context, + _: &'ctx Self::Context, ) -> Self::TransformedStream<'out> { stream::once(future::ready(Ok(event))) } diff --git a/core/src/es/event/adapter/transformer/strategy/custom.rs b/core/src/es/event/adapter/transformer/strategy/custom.rs index 8d41997..1143d39 100644 --- a/core/src/es/event/adapter/transformer/strategy/custom.rs +++ b/core/src/es/event/adapter/transformer/strategy/custom.rs @@ -5,6 +5,10 @@ use futures::Stream; use super::{event, Strategy}; /// [`Strategy`] for some custom conversion provided by [`Customize`]. +/// +/// This [`Strategy`] should be used in case you don't plan to reuse +/// [`Customize`] impl. Otherwise you should implement [`Strategy`] on your +/// custom struct and reuse it. #[derive(Clone, Copy, Debug)] pub struct Custom; @@ -43,7 +47,9 @@ pub trait Customize { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + Self: 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -64,7 +70,10 @@ where type Context = >::Context; type Error = Adapter::Error; type Transformed = Adapter::Transformed; - type TransformedStream<'out> = Adapter::TransformedStream<'out>; + type TransformedStream<'out> + where + Adapter: 'out, + = Adapter::TransformedStream<'out>; fn transform<'me: 'out, 'ctx: 'out, 'out>( adapter: &'me Adapter, diff --git a/core/src/es/event/adapter/transformer/strategy/into.rs b/core/src/es/event/adapter/transformer/strategy/into.rs index ecf1c2a..fd72783 100644 --- a/core/src/es/event/adapter/transformer/strategy/into.rs +++ b/core/src/es/event/adapter/transformer/strategy/into.rs @@ -26,7 +26,10 @@ where type Context = InnerStrategy::Context; type Error = InnerStrategy::Error; type Transformed = IntoEvent; - type TransformedStream<'out> = stream::MapOk< + type TransformedStream<'out> + where + Adapter: 'out, + = stream::MapOk< InnerStrategy::TransformedStream<'out>, IntoFn, >; diff --git a/core/src/es/event/adapter/transformer/strategy/mod.rs b/core/src/es/event/adapter/transformer/strategy/mod.rs index d12f878..03f8500 100644 --- a/core/src/es/event/adapter/transformer/strategy/mod.rs +++ b/core/src/es/event/adapter/transformer/strategy/mod.rs @@ -53,7 +53,9 @@ pub trait Strategy { >::Transformed, >::Error, >, - > + 'out; + > + 'out + where + Adapter: 'out; /// Converts incoming [`Event`] into [`Transformed`]. /// @@ -83,10 +85,12 @@ where type Error = >::Error; type Transformed = >::Transformed; - type TransformedStream<'out> = >::TransformedStream<'out>; + type TransformedStream<'out> + where + 'ctx: 'out, + Adapter: 'out, + Ctx: 'ctx + 'out, + = >::TransformedStream<'out>; fn transform<'me, 'out>( &'me self, diff --git a/core/src/es/event/adapter/transformer/strategy/skip.rs b/core/src/es/event/adapter/transformer/strategy/skip.rs index e7c73d7..94eb330 100644 --- a/core/src/es/event/adapter/transformer/strategy/skip.rs +++ b/core/src/es/event/adapter/transformer/strategy/skip.rs @@ -22,15 +22,15 @@ where type Context = (); type Error = Adapter::Error; type Transformed = Adapter::Transformed; - #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = - stream::Empty>; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Empty>; - #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( - _: &Adapter, + _: &'me Adapter, _: Event, - _: &Self::Context, + _: &'ctx Self::Context, ) -> Self::TransformedStream<'out> { stream::empty() } diff --git a/core/src/es/event/adapter/transformer/strategy/split.rs b/core/src/es/event/adapter/transformer/strategy/split.rs index a668545..bdfe47c 100644 --- a/core/src/es/event/adapter/transformer/strategy/split.rs +++ b/core/src/es/event/adapter/transformer/strategy/split.rs @@ -41,14 +41,15 @@ where type Context = (); type Error = Adapter::Error; type Transformed = ::Item; - #[allow(unused_lifetimes)] // false positive - type TransformedStream<'o> = SplitStream; + type TransformedStream<'o> + where + Adapter: 'o, + = SplitStream; - #[allow(unused_lifetimes)] // false positive fn transform<'me: 'out, 'ctx: 'out, 'out>( - adapter: &Adapter, + adapter: &'me Adapter, event: Event, - _: &Self::Context, + _: &'ctx Self::Context, ) -> Self::TransformedStream<'out> { stream::iter(adapter.split(event)).map(Ok) } diff --git a/core/src/spell.rs b/core/src/spell.rs index a8a333a..06d589e 100644 --- a/core/src/spell.rs +++ b/core/src/spell.rs @@ -4,6 +4,10 @@ use derive_more::Deref; use ref_cast::RefCast; /// Helper to hack around specialization. +/// +/// Used in [`strategy::Customize::Context`][0]. +/// +/// [0]: crate::es::event::adapter::strategy::Customize::Context #[derive(Clone, Copy, Debug, Deref, RefCast)] #[repr(transparent)] pub struct Borrowed(pub T); diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index 58784d9..e2cfe58 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "arcana-example-chat" version = "0.0.0" -edition = "2018" -resolver = "2" +edition = "2021" +rust-version = "1.56" description = "Simple chat app using arcana framework." authors = [ "Ilya Solovyiov ", diff --git a/examples/chat/src/storage/chat.rs b/examples/chat/src/storage/chat.rs index 1552f4d..745c05d 100644 --- a/examples/chat/src/storage/chat.rs +++ b/examples/chat/src/storage/chat.rs @@ -1,7 +1,8 @@ use arcana::es::{ self, - event::adapter::{strategy, Adapt}, + event::adapter::{self, strategy, Adapt, Strategy}, }; +use futures::stream; use crate::event; @@ -26,21 +27,21 @@ impl Adapt for Adapter { } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } impl Adapt> for Adapter { - type Strategy = strategy::Skip; + type Strategy = CustomSkip; } // Chats are private by default. @@ -49,3 +50,29 @@ impl From for event::chat::private::Created { Self } } + +/// Custom [`strategy::Skip`] implementation. +pub struct CustomSkip; + +impl Strategy for CustomSkip +where + Adapter: adapter::Returning, + Adapter::Transformed: 'static, + Adapter::Error: 'static, +{ + type Context = (); + type Error = Adapter::Error; + type Transformed = Adapter::Transformed; + type TransformedStream<'o> + where + Adapter: 'o, + = stream::Empty>; + + fn transform<'me: 'out, 'ctx: 'out, 'out>( + _: &'me Adapter, + _: Event, + _: &'ctx Self::Context, + ) -> Self::TransformedStream<'out> { + stream::empty() + } +} diff --git a/examples/chat/src/storage/email.rs b/examples/chat/src/storage/email.rs index 6e5ef4c..a1db3ba 100644 --- a/examples/chat/src/storage/email.rs +++ b/examples/chat/src/storage/email.rs @@ -62,16 +62,16 @@ impl ) -> Self::Iterator { use either::{Left, Right}; - #[allow(clippy::option_if_let_else)] // use of moved value if let Some(confirmed_by) = event.confirmed_by { - Right(array::IntoIter::new([ - Left(event::email::Added { email: event.email }), - Right(event::email::Confirmed { confirmed_by }), - ])) + Right( + [ + Left(event::email::Added { email: event.email }), + Right(event::email::Confirmed { confirmed_by }), + ] + .into_iter(), + ) } else { - Left(array::IntoIter::new([Left(event::email::Added { - email: event.email, - })])) + Left([Left(event::email::Added { email: event.email })].into_iter()) } } } diff --git a/examples/chat/src/storage/mod.rs b/examples/chat/src/storage/mod.rs index 89ab89d..64f0695 100644 --- a/examples/chat/src/storage/mod.rs +++ b/examples/chat/src/storage/mod.rs @@ -14,6 +14,7 @@ pub enum Event { Email(EmailEvent), } +#[allow(clippy::enum_variant_names)] #[derive(Debug, es::Event, From)] pub enum ChatEvent { Created(event::chat::v1::Created), @@ -38,8 +39,6 @@ pub enum EmailEvent { #[cfg(test)] mod spec { - use std::array; - use arcana::es::{EventAdapter as _, EventSourced as _}; use futures::{future, stream, Stream, TryStreamExt as _}; use serde_json::json; @@ -50,7 +49,6 @@ mod spec { chat, email, event, message, ChatEvent, EmailEvent, Event, MessageEvent, }; - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn chat_adapter() { let mut chat = Option::::None; @@ -79,7 +77,6 @@ mod spec { ); } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter() { let mut email = Option::::None; @@ -116,7 +113,6 @@ mod spec { ); } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn email_adapter_with_corrupted_event() { let corrupted_event = @@ -133,7 +129,6 @@ mod spec { assert_eq!(result.unwrap_err().to_string(), "missing field `email`") } - #[allow(clippy::semicolon_if_nothing_returned)] #[tokio::test] async fn message_adapter() { let mut message = Option::::None; @@ -149,23 +144,26 @@ mod spec { } fn incoming_events() -> impl Stream { - stream::iter(array::IntoIter::new([ - ChatEvent::Created(event::chat::v1::Created).into(), - ChatEvent::PrivateCreated(event::chat::private::Created).into(), - ChatEvent::PublicCreated(event::chat::public::Created).into(), - MessageEvent::Posted(event::message::Posted).into(), - EmailEvent::AddedAndConfirmed( - event::email::v2::AddedAndConfirmed { - email: "hello@world.com".to_owned(), - confirmed_by: None, - }, - ) - .into(), - EmailEvent::RawAddedAndConfirmed(event::Raw::new( - json!({ "email": "raw@event.com", "confirmed_by": "User" }), - event::Version::try_new(1).unwrap(), - )) - .into(), - ])) + stream::iter( + [ + ChatEvent::Created(event::chat::v1::Created).into(), + ChatEvent::PrivateCreated(event::chat::private::Created).into(), + ChatEvent::PublicCreated(event::chat::public::Created).into(), + MessageEvent::Posted(event::message::Posted).into(), + EmailEvent::AddedAndConfirmed( + event::email::v2::AddedAndConfirmed { + email: "hello@world.com".to_owned(), + confirmed_by: None, + }, + ) + .into(), + EmailEvent::RawAddedAndConfirmed(event::Raw::new( + json!({ "email": "raw@event.com", "confirmed_by": "User" }), + event::Version::try_new(1).unwrap(), + )) + .into(), + ] + .into_iter(), + ) } } diff --git a/src/lib.rs b/src/lib.rs index 164a848..145396c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,3 +92,7 @@ pub mod spell { #[doc(inline)] pub use arcana_core::spell::Borrowed; } + +// To avoid lint errors in case `derive` feature is enabled and `es` isn't. +#[cfg(all(feature = "derive", not(feature = "es")))] +use arcana_codegen as _;