├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── transaction-diesel ├── Cargo.toml ├── README.md ├── examples │ ├── simple-crud-combinator │ │ ├── .env │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── migrations │ │ │ ├── .gitkeep │ │ │ └── 20170617072451_create_users │ │ │ │ ├── down.sql │ │ │ │ └── up.sql │ │ └── src │ │ │ ├── db.rs │ │ │ ├── main.rs │ │ │ ├── model.rs │ │ │ └── schema.rs │ └── simple-crud │ │ ├── .env │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── migrations │ │ ├── .gitkeep │ │ └── 20170617072451_create_users │ │ │ ├── down.sql │ │ │ └── up.sql │ │ └── src │ │ ├── db.rs │ │ ├── main.rs │ │ ├── model.rs │ │ └── schema.rs └── src │ └── lib.rs ├── transaction-stm ├── Cargo.toml ├── README.md ├── benches │ └── boxed_vs_branch.rs ├── examples │ ├── impl_struct.rs │ └── simple.rs └── src │ └── lib.rs └── transaction ├── Cargo.toml ├── README.md └── src ├── abort.rs ├── and_then.rs ├── branch.rs ├── branch3.rs ├── branch4.rs ├── err.rs ├── join.rs ├── join3.rs ├── join4.rs ├── join_all.rs ├── lazy.rs ├── lib.rs ├── loop_fn.rs ├── map.rs ├── map_err.rs ├── mdo.rs ├── ok.rs ├── or_else.rs ├── recover.rs ├── repeat.rs ├── result.rs ├── retry.rs ├── then.rs ├── try_abort.rs ├── try_recover.rs └── with_ctx.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # unreleased 2 | 3 | ## transaction-diesel 4 | 5 | * add an example than does not use combinators 6 | 7 | # 0.2.0 2017-06-21 8 | 9 | ## transaction 10 | 11 | * [break] The type parameter `Ctx` of `Transaction` is now an associated type. 12 | * Some methods of `Transaction` is changed to use `IntoTransaction` instead of `Transaction` 13 | * `branchX` methods are added 14 | * `IntoTransaction` is implemented for some Types like `Result` and tupules. 15 | * `repeat`, `retry`, `loop_fn`, `join_all` is added 16 | 17 | ## transaction-diesel 18 | 19 | * catchup transaction update 20 | * update diesel dependency 21 | 22 | # 0.1.0 2017-06-06 23 | * first release 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "transaction", 4 | "transaction-diesel", 5 | "transaction-stm", 6 | "transaction-diesel/examples/simple-crud", 7 | "transaction-diesel/examples/simple-crud-combinator" 8 | ] 9 | 10 | [replace] 11 | "transaction:0.2.1" = { path = "transaction" } 12 | "transaction-diesel:0.2.0" = { path = "transaction-diesel" } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # transaction-rs 2 | The transaction abstraction library and its executors. 3 | 4 | This crate abstracts over transactions like STM, SQL transactions and so on. It is composable via combinators and does DI of transactions. 5 | 6 | The basic idea is representing contracts of "this computation must be run under a transaction" as types. The trait `Transaction` represents a sequence of computation that must be run under a transaction. And transactions are composable (sequencable) using `then`, `and_then`, `or_else`, hence you can use it like values wrapped in `Result`. Since it represents computation to be run in data, some types respond to control operators are provided: `abort` for `?`, `repeat` for `for`, `loop_fn` for `loop` and `branch` for (join point of) `if` and so on. As all the combinators have its own result type, no dispatches are done at execution time thus it is zero-cost. 7 | 8 | Another feature is it does DI of transaction. For database transaction, it means that it injects DB connection from the context. 9 | 10 | 11 | See [transaction-stm/examples](transaction-stm/examples) or [transaction-diesel/examples](transaction-diesel/examples) for usage. 12 | 13 | 14 | # Documentatins 15 | 16 | * [transaction](https://docs.rs/transaction) 17 | * [transaction-diesel](https://docs.rs/transaction-diesel) 18 | * [transaction-stm](https://docs.rs/transaction-stm) 19 | -------------------------------------------------------------------------------- /transaction-diesel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sunrin SHIMURA (keen) <3han5chou7@gmail.com>"] 3 | name = "transaction-diesel" 4 | version = "0.2.0" 5 | license = "MIT" 6 | description = "transaction abstraction of diesel" 7 | readme = "README.md" 8 | documentation = "http://docs.rs/transaction-diesel/0.2.0/transaction-diesel/" 9 | repository = "https://github.com/KeenS/transaction-rs" 10 | keywords = ["transaction", "diesel"] 11 | categories = ["rust-patterns"] 12 | 13 | [dependencies] 14 | diesel = ">=0.12.0, <= 0.13" 15 | transaction = "0.2.1" -------------------------------------------------------------------------------- /transaction-diesel/README.md: -------------------------------------------------------------------------------- 1 | # transaction-diesel 2 | 3 | A [transaction](../transaction) runner for [diesel](https://github.com/diesel-rs/diesel) 4 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://user:password@localhost/txdiesel 2 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sunrin SHIMURA (keen) <3han5chou7@gmail.com>"] 3 | name = "simple-crud-combinator" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | dotenv = "0.10.0" 8 | transaction = "0.2.1" 9 | transaction-diesel = {path ="../../"} 10 | 11 | [dependencies.diesel] 12 | features = ["postgres"] 13 | version = "0.13.0" 14 | 15 | [dependencies.diesel_codegen] 16 | features = ["postgres"] 17 | version = "0.13.0" 18 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/README.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | 3 | * diesel_cli 4 | * docker 5 | 6 | # Building 7 | 8 | ``` console 9 | $ docker-compose up -d 10 | $ diesel database setup 11 | $ cargo build 12 | ``` 13 | 14 | # Running 15 | 16 | ``` console 17 | $ cargo run 18 | created user: User { id: 1, name: "keen" } 19 | updated user: User { id: 1, name: "KeenS" } 20 | ``` 21 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/docker-compose.yml: -------------------------------------------------------------------------------- 1 | postgres-data: 2 | image: busybox 3 | volumes: 4 | - /var/lib/postgresql/txdiesel-data 5 | container_name: txdiesel-postgres-datastore 6 | 7 | postgresql: 8 | image: postgres 9 | environment: 10 | POSTGRES_USER: user 11 | POSTGRES_PASSWORD: password 12 | ports: 13 | - "5432:5432" 14 | volumes_from: 15 | - postgres-data 16 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeenS/transaction-rs/ef7b3952a40350471edf217207651ff79d682484/transaction-diesel/examples/simple-crud-combinator/migrations/.gitkeep -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/migrations/20170617072451_create_users/down.sql: -------------------------------------------------------------------------------- 1 | -- -*- mode: sql; sql-product: postgres; -*- 2 | -- This file should undo anything in `up.sql` 3 | 4 | DROP TABLE users; 5 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/migrations/20170617072451_create_users/up.sql: -------------------------------------------------------------------------------- 1 | -- -*- mode: sql; sql-product: postgres; -*- 2 | -- Your SQL goes here 3 | 4 | CREATE TABLE users 5 | ( 6 | id bigserial PRIMARY KEY NOT NULL, 7 | name varchar NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/src/db.rs: -------------------------------------------------------------------------------- 1 | use diesel; 2 | use diesel::prelude::*; 3 | use diesel::pg::PgConnection; 4 | use diesel::result::Error; 5 | use transaction::prelude::*; 6 | use transaction_diesel::DieselContext; 7 | use transaction_diesel::with_conn; 8 | 9 | use model::*; 10 | 11 | type Ctx<'a> = DieselContext<'a, PgConnection>; 12 | // Until Rust supports `impl Trait`, we need to box `Transaction`s when returning from functions. 13 | type BoxTx<'a, T> = Box, Item = T, Err = Error> + 'a>; 14 | 15 | pub fn create_user<'a>(name: &'a str) -> BoxTx<'a, User> { 16 | use schema::users::table; 17 | // Connections are injected via transaction. 18 | // Get it using `with_conn` 19 | with_conn(move |cn| { 20 | diesel::insert(&NewUser { name: name }) 21 | .into(table) 22 | .get_result(cn) 23 | }) 24 | // box it 25 | .boxed() 26 | } 27 | 28 | pub fn find_user<'a>(id: i64) -> BoxTx<'a, Option> { 29 | use schema::users::dsl::users; 30 | with_conn(move |cn| users.find(id).get_result(cn).optional()).boxed() 31 | } 32 | 33 | pub fn update_user<'a>(id: i64, name: &'a str) -> BoxTx<'a, Option<()>> { 34 | use schema::users::dsl; 35 | with_conn(move |cn| { 36 | diesel::update(dsl::users.find(id)) 37 | .set(dsl::name.eq(name)) 38 | .execute(cn) 39 | .map(|_| ()) 40 | .optional() 41 | }).boxed() 42 | } 43 | 44 | 45 | pub fn delete_user<'a>(id: i64) -> BoxTx<'a, Option<()>> { 46 | use schema::users::dsl::users; 47 | with_conn(move |cn| { 48 | diesel::delete(users.find(id)) 49 | .execute(cn) 50 | .map(|_| ()) 51 | .optional() 52 | }).boxed() 53 | } 54 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate diesel; 3 | #[macro_use] 4 | extern crate diesel_codegen; 5 | extern crate dotenv; 6 | extern crate transaction; 7 | extern crate transaction_diesel; 8 | 9 | mod schema; 10 | mod model; 11 | mod db; 12 | 13 | use transaction::prelude::*; 14 | use diesel::pg::PgConnection; 15 | 16 | pub fn establish_connection() -> PgConnection { 17 | use dotenv::dotenv; 18 | use std::env; 19 | use diesel::prelude::*; 20 | 21 | dotenv().ok(); 22 | 23 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 24 | PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url)) 25 | } 26 | 27 | fn main() { 28 | let conn = establish_connection(); 29 | // composed computation of DB operations 30 | let tx = db::create_user("keen") 31 | // Transactions can be sequenced using `and_then` 32 | .and_then(move |user| { 33 | println!("created user: {:?}", user); 34 | db::update_user(user.id, "KeenS") 35 | // to pass values by move, you need to inject them by `join` 36 | .join(ok(user)) 37 | .and_then(|(res, user)| match res { 38 | None => { 39 | println!("user not found"); 40 | // when you branch and return different `Transaction`s it is an error. Some operation is needed. 41 | // One option is boxing all the transactions returning from all the branches. 42 | // Another option is using `branch` API. Use `first` in one branch and `second` in the other branch. 43 | ok(()).branch().first() 44 | } 45 | Some(()) => db::find_user(user.id) 46 | .and_then(move |maybe_updated_user| { 47 | match maybe_updated_user { 48 | None => { 49 | println!("user not found"); 50 | ok(()).branch().first() 51 | }, 52 | Some(updated_user) => { 53 | println!("updated user: {:?}", updated_user); 54 | db::delete_user(updated_user.id) 55 | .map(|res| match res { 56 | None => { 57 | println!("user not found"); 58 | }, 59 | Some(()) => () 60 | }) 61 | .branch().second() 62 | } 63 | } 64 | }).branch().second(), 65 | 66 | }) 67 | }); 68 | 69 | // to run the composed computation, use `transaction_diesel::run`. 70 | transaction_diesel::run(&conn, tx).unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/src/model.rs: -------------------------------------------------------------------------------- 1 | use schema::*; 2 | 3 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] 4 | #[derive(Queryable)] 5 | pub struct User { 6 | pub id: i64, 7 | pub name: String, 8 | } 9 | 10 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] 11 | #[derive(Insertable)] 12 | #[table_name = "users"] 13 | pub struct NewUser<'a> { 14 | pub name: &'a str, 15 | } 16 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud-combinator/src/schema.rs: -------------------------------------------------------------------------------- 1 | infer_schema!("dotenv:DATABASE_URL"); 2 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://user:password@localhost/txdiesel 2 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sunrin SHIMURA (keen) <3han5chou7@gmail.com>"] 3 | name = "simple-crud" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | dotenv = "0.10.0" 8 | transaction = "0.2.1" 9 | transaction-diesel = {path ="../../"} 10 | 11 | [dependencies.diesel] 12 | features = ["postgres"] 13 | version = "0.13.0" 14 | 15 | [dependencies.diesel_codegen] 16 | features = ["postgres"] 17 | version = "0.13.0" 18 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/README.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | 3 | * diesel_cli 4 | * docker 5 | 6 | # Building 7 | 8 | ``` console 9 | $ docker-compose up -d 10 | $ diesel database setup 11 | $ cargo build 12 | ``` 13 | 14 | # Running 15 | 16 | ``` console 17 | $ cargo run 18 | created user: User { id: 1, name: "keen" } 19 | updated user: User { id: 1, name: "KeenS" } 20 | ``` 21 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/docker-compose.yml: -------------------------------------------------------------------------------- 1 | postgres-data: 2 | image: busybox 3 | volumes: 4 | - /var/lib/postgresql/txdiesel-data 5 | container_name: txdiesel-postgres-datastore 6 | 7 | postgresql: 8 | image: postgres 9 | environment: 10 | POSTGRES_USER: user 11 | POSTGRES_PASSWORD: password 12 | ports: 13 | - "5432:5432" 14 | volumes_from: 15 | - postgres-data 16 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeenS/transaction-rs/ef7b3952a40350471edf217207651ff79d682484/transaction-diesel/examples/simple-crud/migrations/.gitkeep -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/migrations/20170617072451_create_users/down.sql: -------------------------------------------------------------------------------- 1 | -- -*- mode: sql; sql-product: postgres; -*- 2 | -- This file should undo anything in `up.sql` 3 | 4 | DROP TABLE users; 5 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/migrations/20170617072451_create_users/up.sql: -------------------------------------------------------------------------------- 1 | -- -*- mode: sql; sql-product: postgres; -*- 2 | -- Your SQL goes here 3 | 4 | CREATE TABLE users 5 | ( 6 | id bigserial PRIMARY KEY NOT NULL, 7 | name varchar NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/src/db.rs: -------------------------------------------------------------------------------- 1 | use diesel; 2 | use diesel::prelude::*; 3 | use diesel::pg::PgConnection; 4 | use diesel::result::Error; 5 | use transaction::prelude::*; 6 | use transaction_diesel::DieselContext; 7 | use transaction_diesel::with_conn; 8 | 9 | use model::*; 10 | 11 | type Ctx<'a> = DieselContext<'a, PgConnection>; 12 | // Until Rust supports `impl Trait`, we need to box `Transaction`s when returning from functions. 13 | type BoxTx<'a, T> = Box, Item = T, Err = Error> + 'a>; 14 | 15 | pub fn create_user<'a>(name: &'a str) -> BoxTx<'a, User> { 16 | use schema::users::table; 17 | // Connections are injected via transaction. 18 | // Get it using `with_conn` 19 | with_conn(move |cn| { 20 | diesel::insert(&NewUser { name: name }) 21 | .into(table) 22 | .get_result(cn) 23 | }) 24 | // box it 25 | .boxed() 26 | } 27 | 28 | pub fn find_user<'a>(id: i64) -> BoxTx<'a, Option> { 29 | use schema::users::dsl::users; 30 | with_conn(move |cn| users.find(id).get_result(cn).optional()).boxed() 31 | } 32 | 33 | pub fn update_user<'a>(id: i64, name: &'a str) -> BoxTx<'a, Option<()>> { 34 | use schema::users::dsl; 35 | with_conn(move |cn| { 36 | diesel::update(dsl::users.find(id)) 37 | .set(dsl::name.eq(name)) 38 | .execute(cn) 39 | .map(|_| ()) 40 | .optional() 41 | }).boxed() 42 | } 43 | 44 | 45 | pub fn delete_user<'a>(id: i64) -> BoxTx<'a, Option<()>> { 46 | use schema::users::dsl::users; 47 | with_conn(move |cn| { 48 | diesel::delete(users.find(id)) 49 | .execute(cn) 50 | .map(|_| ()) 51 | .optional() 52 | }).boxed() 53 | } 54 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate diesel; 3 | #[macro_use] 4 | extern crate diesel_codegen; 5 | extern crate dotenv; 6 | extern crate transaction; 7 | extern crate transaction_diesel; 8 | 9 | mod schema; 10 | mod model; 11 | mod db; 12 | 13 | use transaction::prelude::*; 14 | use diesel::pg::PgConnection; 15 | use diesel::result::Error; 16 | 17 | pub fn establish_connection() -> PgConnection { 18 | use dotenv::dotenv; 19 | use std::env; 20 | use diesel::prelude::*; 21 | 22 | dotenv().ok(); 23 | 24 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 25 | PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url)) 26 | } 27 | 28 | fn main() { 29 | let conn = establish_connection(); 30 | // composed computation of DB operations 31 | // you can get transaction context using `with_ctx` 32 | let tx = with_ctx(|ctx| -> Result<(), Error> { 33 | // if you have context, you can run a transaction using `run`. 34 | // Since it returns a `Result` value, `?` operators can be applied; 35 | let user = db::create_user("keen").run(ctx)?; 36 | println!("created user: {:?}", user); 37 | let res = db::update_user(user.id, "KeenS").run(ctx)?; 38 | match res { 39 | None => { 40 | println!("user not found"); 41 | return Ok(()); 42 | } 43 | Some(()) => (), 44 | }; 45 | let updated_user = match db::find_user(user.id).run(ctx)? { 46 | None => { 47 | println!("user not found"); 48 | return Ok(()); 49 | } 50 | Some(u) => u, 51 | }; 52 | 53 | println!("updated user: {:?}", updated_user); 54 | match db::delete_user(updated_user.id).run(ctx)? { 55 | None => { 56 | println!("user not found"); 57 | } 58 | Some(()) => (), 59 | }; 60 | Ok(()) 61 | }); 62 | // to run the composed computation, use `transaction_diesel::run`. 63 | transaction_diesel::run(&conn, tx).unwrap() 64 | } 65 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/src/model.rs: -------------------------------------------------------------------------------- 1 | use schema::*; 2 | 3 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] 4 | #[derive(Queryable)] 5 | pub struct User { 6 | pub id: i64, 7 | pub name: String, 8 | } 9 | 10 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] 11 | #[derive(Insertable)] 12 | #[table_name = "users"] 13 | pub struct NewUser<'a> { 14 | pub name: &'a str, 15 | } 16 | -------------------------------------------------------------------------------- /transaction-diesel/examples/simple-crud/src/schema.rs: -------------------------------------------------------------------------------- 1 | infer_schema!("dotenv:DATABASE_URL"); 2 | -------------------------------------------------------------------------------- /transaction-diesel/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A transaction runner for diesel 2 | 3 | extern crate diesel; 4 | extern crate transaction; 5 | use transaction::*; 6 | use std::marker::PhantomData; 7 | 8 | /// run the given function insed a transaction using the given connection. 9 | pub fn run<'a, Cn, T, E, Tx>(cn: &'a Cn, tx: Tx) -> Result 10 | where 11 | Cn: diesel::Connection, 12 | E: From, 13 | Tx: Transaction, Item = T, Err = E>, 14 | { 15 | cn.clone().transaction( 16 | || tx.run(&mut DieselContext::new(cn)), 17 | ) 18 | } 19 | 20 | /// run the given function insed a transaction using the given connection but do not commit it. 21 | /// Panics if the given function returns an Err. 22 | /// This is usefull for testing 23 | pub fn test_run<'a, Cn, T, E, Tx>(cn: &'a Cn, tx: Tx) -> T 24 | where 25 | Cn: diesel::Connection, 26 | E: From, 27 | Tx: Transaction, Item = T, Err = E>, 28 | { 29 | cn.clone().test_transaction( 30 | || tx.run(&mut DieselContext::new(cn)), 31 | ) 32 | } 33 | 34 | /// diesel transaction object. 35 | pub struct DieselContext<'a, Cn: 'a> { 36 | conn: &'a Cn, 37 | _phantom: PhantomData<()>, 38 | } 39 | 40 | impl<'a, Cn> DieselContext<'a, Cn> { 41 | // never pub this function 42 | fn new(conn: &'a Cn) -> Self { 43 | DieselContext { 44 | conn: conn, 45 | _phantom: PhantomData, 46 | } 47 | } 48 | 49 | fn conn(&self) -> &'a Cn { 50 | &self.conn 51 | } 52 | } 53 | 54 | /// Receive the connection from the executing transaction and perform computation. 55 | pub fn with_conn<'a, Conn, F, T, E>(f: F) -> WithConn<'a, Conn, F> 56 | where 57 | F: Fn(&'a Conn) -> Result, 58 | { 59 | WithConn { 60 | f: f, 61 | _phantom: PhantomData, 62 | } 63 | } 64 | 65 | /// The result of `with_conn` 66 | #[derive(Debug)] 67 | pub struct WithConn<'a, Conn: 'a, F> { 68 | f: F, 69 | _phantom: PhantomData<&'a Conn>, 70 | } 71 | 72 | impl<'a, Conn, T, E, F> Transaction for WithConn<'a, Conn, F> 73 | where 74 | F: Fn(&'a Conn) -> Result, 75 | { 76 | type Ctx = DieselContext<'a, Conn>; 77 | type Item = T; 78 | type Err = E; 79 | fn run(&self, ctx: &mut DieselContext<'a, Conn>) -> Result { 80 | (self.f)(ctx.conn()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /transaction-stm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sunrin SHIMURA (keen) <3han5chou7@gmail.com>"] 3 | name = "transaction-stm" 4 | version = "0.2.0" 5 | license = "MIT" 6 | description = "transaction abstraction of stm" 7 | readme = "README.md" 8 | documentation = "http://docs.rs/transaction-stm/0.2.0/transaction-stm/" 9 | repository = "https://github.com/KeenS/transaction-rs" 10 | keywords = ["transaction", "stm"] 11 | categories = ["rust-patterns", "concurrency"] 12 | 13 | [dependencies] 14 | stm = "0.2.4" 15 | transaction = "0.2.1" -------------------------------------------------------------------------------- /transaction-stm/README.md: -------------------------------------------------------------------------------- 1 | # transaction-stm 2 | 3 | A [transaction](../transaction) runner for [stm](https://github.com/Marthog/rust-stm) 4 | -------------------------------------------------------------------------------- /transaction-stm/benches/boxed_vs_branch.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate stm; 4 | extern crate transaction; 5 | extern crate transaction_stm; 6 | extern crate test; 7 | 8 | use transaction::prelude::*; 9 | use transaction_stm::{run, with_tx}; 10 | use test::Bencher; 11 | 12 | type BoxTx<'a, T> = Box + 'a>; 13 | 14 | fn inc<'a>(x: &'a stm::TVar) -> BoxTx<'a, usize> { 15 | with_tx(move |ctx| { 16 | let xv = ctx.read(&x)?; 17 | ctx.write(&x, xv + 1)?; 18 | Ok(xv) 19 | }).boxed() 20 | } 21 | 22 | fn halve<'a>(x: &'a stm::TVar) -> BoxTx<'a, usize> { 23 | with_tx(move |ctx| { 24 | let xv = ctx.read(&x)?; 25 | ctx.write(&x, xv / 2)?; 26 | Ok(xv) 27 | }).boxed() 28 | } 29 | 30 | 31 | #[bench] 32 | fn bench_branch(b: &mut Bencher) { 33 | let x = stm::TVar::new(0); 34 | let tx = repeat(1_000, |i| if i % 2 == 0 { 35 | inc(&x).and_then(|_| halve(&x)).map(|_| ()).branch().first() 36 | } else { 37 | inc(&x).map(|_| ()).branch().second() 38 | 39 | }); 40 | b.iter(|| run(&tx)); 41 | } 42 | 43 | #[bench] 44 | fn bench_boxed(b: &mut Bencher) { 45 | let x = stm::TVar::new(0); 46 | let tx = repeat(1_000, |i| if i % 2 == 0 { 47 | inc(&x).and_then(|_| halve(&x)).map(|_| ()).boxed() 48 | } else { 49 | inc(&x).map(|_| ()).boxed() 50 | 51 | }); 52 | b.iter(|| run(&tx)); 53 | } 54 | 55 | #[bench] 56 | fn bench_nonboxing(b: &mut Bencher) { 57 | let x = stm::TVar::new(0); 58 | let tx = repeat(1_000, |i| if i % 2 == 0 { 59 | with_tx(|ctx| { 60 | let xv = ctx.read(&x)?; 61 | ctx.write(&x, xv + 1)?; 62 | Ok(xv) 63 | }).and_then(|_| { 64 | with_tx(|ctx| { 65 | let xv = ctx.read(&x)?; 66 | ctx.write(&x, xv / 2)?; 67 | Ok(xv) 68 | }) 69 | }) 70 | .map(|_| ()) 71 | .branch() 72 | .first() 73 | } else { 74 | with_tx(|ctx| { 75 | let xv = ctx.read(&x)?; 76 | ctx.write(&x, xv + 1)?; 77 | Ok(xv) 78 | }).map(|_| ()) 79 | .branch() 80 | .second() 81 | 82 | }); 83 | b.iter(|| run(&tx)); 84 | } 85 | 86 | 87 | // running 3 tests 88 | // test bench_boxed ... bench: 202,656 ns/iter (+/- 7,825) 89 | // test bench_branch ... bench: 188,261 ns/iter (+/- 13,502) 90 | // test bench_nonboxing ... bench: 176,353 ns/iter (+/- 13,134) 91 | 92 | // test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured; 0 filtered out 93 | -------------------------------------------------------------------------------- /transaction-stm/examples/impl_struct.rs: -------------------------------------------------------------------------------- 1 | extern crate stm; 2 | extern crate transaction; 3 | extern crate transaction_stm; 4 | 5 | use transaction::prelude::*; 6 | use transaction_stm::{run, with_tx}; 7 | 8 | struct Data { 9 | x: stm::TVar, 10 | y: stm::TVar, 11 | } 12 | 13 | type BoxTx<'a, T> = Box + 'a>; 14 | 15 | impl Data { 16 | fn inc_x(&self) -> BoxTx { 17 | with_tx(move |ctx| { 18 | let xv = ctx.read(&self.x)?; 19 | ctx.write(&self.x, xv + 1)?; 20 | Ok(xv) 21 | }).boxed() 22 | } 23 | fn inc_y(&self) -> BoxTx { 24 | with_tx(move |ctx| { 25 | let yv = ctx.read(&self.y)?; 26 | ctx.write(&self.y, yv + 1)?; 27 | Ok(yv) 28 | }).boxed() 29 | } 30 | 31 | fn inc_xy(&self) -> BoxTx { 32 | self.inc_x().and_then(move |_| self.inc_y()).boxed() 33 | } 34 | fn add(&self) -> BoxTx { 35 | with_tx(move |ctx| { 36 | let xv = ctx.read(&self.x)?; 37 | let yv = ctx.read(&self.y)?; 38 | Ok(xv + yv) 39 | }).boxed() 40 | } 41 | } 42 | 43 | 44 | 45 | 46 | fn main() { 47 | let data = Data { 48 | x: stm::TVar::new(0), 49 | y: stm::TVar::new(0), 50 | }; 51 | 52 | let ret = run(&data.inc_xy().and_then(|_| data.add())); 53 | 54 | assert_eq!(ret, 2); 55 | } 56 | -------------------------------------------------------------------------------- /transaction-stm/examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate stm; 2 | extern crate transaction; 3 | extern crate transaction_stm; 4 | 5 | use transaction::prelude::*; 6 | use transaction_stm::{run, with_tx}; 7 | 8 | fn main() { 9 | let x = stm::TVar::new(0); 10 | let y = stm::TVar::new(0); 11 | 12 | let inc_xy = with_tx(|ctx| { 13 | let xv = ctx.read(&x)?; 14 | ctx.write(&x, xv + 1)?; 15 | Ok(xv) 16 | }).and_then(|_| { 17 | with_tx(|ctx| { 18 | let yv = ctx.read(&y)?; 19 | ctx.write(&y, yv + 1)?; 20 | Ok(yv) 21 | }) 22 | }) 23 | .and_then(|_| with_tx(|ctx| Ok(ctx.read(&x)? + ctx.read(&y)?))); 24 | let ret = run(&inc_xy); 25 | assert_eq!(ret, 2); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /transaction-stm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Run the `stm` transaction 2 | //! 3 | //! # Examples 4 | //! ```rust 5 | //! extern crate stm; 6 | //! extern crate transaction; 7 | //! extern crate transaction_stm; 8 | //! 9 | //! use transaction::{Transaction, with_ctx}; 10 | //! use transaction_stm::run; 11 | //! 12 | //! fn main() { 13 | //! let x = stm::TVar::new(0); 14 | //! let y = stm::TVar::new(0); 15 | //! 16 | //! let inc_xy = 17 | //! with_ctx(|ctx: &mut stm::Transaction| { 18 | //! let xv = ctx.read(&x)?; 19 | //! ctx.write(&x, xv + 1)?; 20 | //! Ok(xv) 21 | //! }) 22 | //! .and_then(|_| { 23 | //! with_ctx(|ctx: &mut stm::Transaction| { 24 | //! let yv = ctx.read(&y)?; 25 | //! ctx.write(&y, yv + 1)?; 26 | //! Ok(yv) 27 | //! }) 28 | //! }) 29 | //! .and_then(|_| { 30 | //! with_ctx(|ctx: &mut stm::Transaction| { 31 | //! Ok(ctx.read(&x)? + ctx.read(&y)?) 32 | //! }) 33 | //! }); 34 | //! let ret = run(&inc_xy); 35 | //! assert_eq!(ret, 2); 36 | //! 37 | //! } 38 | //! ``` 39 | 40 | 41 | 42 | extern crate stm; 43 | extern crate transaction; 44 | 45 | use transaction::Transaction; 46 | use stm::Transaction as Stm; 47 | 48 | 49 | /// Run the `stm` transaction 50 | pub fn run(tx: &Tx) -> T 51 | where 52 | Tx: Transaction, 53 | { 54 | Stm::with(|stm| tx.run(stm)) 55 | } 56 | 57 | pub fn with_tx(f: F) -> WithTx 58 | where 59 | F: Fn(&mut Stm) -> Result, 60 | { 61 | WithTx { f: f } 62 | } 63 | 64 | pub struct WithTx { 65 | f: F, 66 | } 67 | 68 | impl Transaction for WithTx 69 | where 70 | F: Fn(&mut Stm) -> Result, 71 | { 72 | type Ctx = Stm; 73 | type Item = T; 74 | type Err = E; 75 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 76 | let WithTx { ref f } = *self; 77 | f(ctx) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /transaction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sunrin SHIMURA (keen) <3han5chou7@gmail.com>"] 3 | name = "transaction" 4 | version = "0.2.1" 5 | license = "MIT" 6 | description = "transaction abstraction library (a.k.a. transaction monad)" 7 | readme = "README.md" 8 | documentation = "http://docs.rs/transaction/0.2.1/transaction/" 9 | repository = "https://github.com/KeenS/transaction-rs" 10 | keywords = ["transaction"] 11 | categories = ["rust-patterns"] 12 | 13 | [dependencies] 14 | mdo = {version = "0.3.0", optional = true} 15 | -------------------------------------------------------------------------------- /transaction/README.md: -------------------------------------------------------------------------------- 1 | # Transaction 2 | 3 | An zero cost transaction abstraction library. 4 | This crate provide the ways to abstract and compinate transactions. 5 | Combinated comptations are run under a transaction. 6 | Not only it can be composed run under a transaction, it also *requires* computations are composed and run under a transaction. 7 | 8 | To run the transactions, use crates like [`transaction-stm`](../transaction-stm) or [`transaction-diesel`](../transaction-diesel) 9 | -------------------------------------------------------------------------------- /transaction/src/abort.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | /// Take the previous successfull value of computation and abort the 6 | /// transaction. 7 | pub fn abort(a: A, f: F) -> Abort 8 | where 9 | A: IntoTransaction, 10 | F: Fn(A::Item) -> A::Err, 11 | { 12 | Abort { 13 | tx: a.into_transaction(), 14 | f: f, 15 | _phantom: PhantomData, 16 | } 17 | } 18 | 19 | 20 | /// The result of `abort` 21 | #[derive(Debug)] 22 | #[must_use] 23 | pub struct Abort { 24 | tx: Tx, 25 | f: F, 26 | _phantom: PhantomData, 27 | } 28 | 29 | impl Transaction for Abort 30 | where 31 | Tx: Transaction, 32 | F: Fn(Tx::Item) -> Tx::Err, 33 | { 34 | type Ctx = Tx::Ctx; 35 | type Item = T; 36 | type Err = Tx::Err; 37 | 38 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 39 | let &Abort { ref tx, ref f, .. } = self; 40 | match tx.run(ctx) { 41 | Ok(r) => Err(f(r)), 42 | Err(e) => Err(e), 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /transaction/src/and_then.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn and_then(a: A, f: F) -> AndThen 6 | where 7 | A: IntoTransaction, 8 | B: IntoTransaction, 9 | F: Fn(A::Item) -> B, 10 | { 11 | AndThen { 12 | tx: a.into_transaction(), 13 | f: f, 14 | _phantom: PhantomData, 15 | } 16 | } 17 | 18 | 19 | /// The result of `and_then` 20 | #[derive(Debug)] 21 | #[must_use] 22 | pub struct AndThen { 23 | tx: Tx1, 24 | f: F, 25 | _phantom: PhantomData, 26 | } 27 | 28 | 29 | impl Transaction for AndThen 30 | where 31 | Tx2: IntoTransaction, 32 | Tx: Transaction, 33 | F: Fn(Tx::Item) -> Tx2, 34 | { 35 | type Ctx = Tx::Ctx; 36 | type Item = Tx2::Item; 37 | type Err = Tx2::Err; 38 | 39 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 40 | let &AndThen { ref tx, ref f, .. } = self; 41 | tx.run(ctx).and_then( 42 | |item| f(item).into_transaction().run(ctx), 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /transaction/src/branch.rs: -------------------------------------------------------------------------------- 1 | use Transaction; 2 | 3 | /// BranchBuilder 4 | #[derive(Debug)] 5 | #[must_use] 6 | pub struct BranchBuilder(Tx); 7 | 8 | impl BranchBuilder { 9 | pub fn new(tx: Tx) -> Self { 10 | BranchBuilder(tx) 11 | } 12 | 13 | pub fn first(self) -> Branch { 14 | Branch::B1(self.0) 15 | } 16 | 17 | pub fn second(self) -> Branch { 18 | Branch::B2(self.0) 19 | } 20 | } 21 | 22 | /// The result of `branch` 23 | #[derive(Debug)] 24 | #[must_use] 25 | pub enum Branch { 26 | B1(Tx1), 27 | B2(Tx2), 28 | } 29 | 30 | 31 | impl Transaction for Branch 32 | where 33 | Tx1: Transaction, 34 | Tx2: Transaction< 35 | Ctx = Tx1::Ctx, 36 | Item = Tx1::Item, 37 | Err = Tx1::Err, 38 | >, 39 | { 40 | type Ctx = Tx1::Ctx; 41 | type Item = Tx1::Item; 42 | type Err = Tx1::Err; 43 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 44 | match *self { 45 | Branch::B1(ref tx) => tx.run(ctx), 46 | Branch::B2(ref tx) => tx.run(ctx), 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /transaction/src/branch3.rs: -------------------------------------------------------------------------------- 1 | use Transaction; 2 | 3 | /// Branch3Builder 4 | #[derive(Debug)] 5 | #[must_use] 6 | pub struct Branch3Builder(Tx); 7 | 8 | impl Branch3Builder { 9 | pub fn new(tx: Tx) -> Self { 10 | Branch3Builder(tx) 11 | } 12 | 13 | pub fn first(self) -> Branch3 { 14 | Branch3::B1(self.0) 15 | } 16 | 17 | pub fn second(self) -> Branch3 { 18 | Branch3::B2(self.0) 19 | } 20 | 21 | pub fn third(self) -> Branch3 { 22 | Branch3::B3(self.0) 23 | } 24 | } 25 | 26 | /// The result of `branch3` 27 | #[derive(Debug)] 28 | #[must_use] 29 | pub enum Branch3 { 30 | B1(Tx1), 31 | B2(Tx2), 32 | B3(Tx3), 33 | } 34 | 35 | impl Transaction for Branch3 36 | where 37 | Tx1: Transaction, 38 | Tx2: Transaction< 39 | Ctx = Tx1::Ctx, 40 | Item = Tx1::Item, 41 | Err = Tx1::Err, 42 | >, 43 | Tx3: Transaction< 44 | Ctx = Tx1::Ctx, 45 | Item = Tx1::Item, 46 | Err = Tx1::Err, 47 | >, 48 | { 49 | type Ctx = Tx1::Ctx; 50 | type Item = Tx1::Item; 51 | type Err = Tx1::Err; 52 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 53 | match *self { 54 | Branch3::B1(ref tx) => tx.run(ctx), 55 | Branch3::B2(ref tx) => tx.run(ctx), 56 | Branch3::B3(ref tx) => tx.run(ctx), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /transaction/src/branch4.rs: -------------------------------------------------------------------------------- 1 | use Transaction; 2 | 3 | /// Branch4Builder 4 | #[derive(Debug)] 5 | #[must_use] 6 | pub struct Branch4Builder(Tx); 7 | 8 | impl Branch4Builder { 9 | pub fn new(tx: Tx) -> Self { 10 | Branch4Builder(tx) 11 | } 12 | 13 | pub fn first(self) -> Branch4 { 14 | Branch4::B1(self.0) 15 | } 16 | 17 | pub fn second(self) -> Branch4 { 18 | Branch4::B2(self.0) 19 | } 20 | 21 | pub fn third(self) -> Branch4 { 22 | Branch4::B3(self.0) 23 | } 24 | 25 | pub fn fourth(self) -> Branch4 { 26 | Branch4::B4(self.0) 27 | } 28 | } 29 | 30 | 31 | /// The result of `branch4` 32 | #[derive(Debug)] 33 | #[must_use] 34 | pub enum Branch4 { 35 | B1(Tx1), 36 | B2(Tx2), 37 | B3(Tx3), 38 | B4(Tx4), 39 | } 40 | 41 | impl Transaction for Branch4 42 | where 43 | Tx1: Transaction, 44 | Tx2: Transaction< 45 | Ctx = Tx1::Ctx, 46 | Item = Tx1::Item, 47 | Err = Tx1::Err, 48 | >, 49 | Tx3: Transaction< 50 | Ctx = Tx1::Ctx, 51 | Item = Tx1::Item, 52 | Err = Tx1::Err, 53 | >, 54 | Tx4: Transaction< 55 | Ctx = Tx1::Ctx, 56 | Item = Tx1::Item, 57 | Err = Tx1::Err, 58 | >, 59 | { 60 | type Ctx = Tx1::Ctx; 61 | type Item = Tx1::Item; 62 | type Err = Tx1::Err; 63 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 64 | match *self { 65 | Branch4::B1(ref tx) => tx.run(ctx), 66 | Branch4::B2(ref tx) => tx.run(ctx), 67 | Branch4::B3(ref tx) => tx.run(ctx), 68 | Branch4::B4(ref tx) => tx.run(ctx), 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /transaction/src/err.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use Transaction; 4 | 5 | /// make a error transaction value. 6 | pub fn err(e: E) -> TxErr { 7 | TxErr { 8 | err: e, 9 | _phantom: PhantomData, 10 | } 11 | } 12 | 13 | 14 | /// The result of `err` 15 | #[derive(Debug)] 16 | #[must_use] 17 | pub struct TxErr { 18 | err: E, 19 | _phantom: PhantomData<(Ctx, T)>, 20 | } 21 | 22 | impl Transaction for TxErr 23 | where 24 | E: Clone, 25 | { 26 | type Ctx = Ctx; 27 | type Item = T; 28 | type Err = E; 29 | fn run(&self, _ctx: &mut Self::Ctx) -> Result { 30 | Err(self.err.clone()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /transaction/src/join.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | pub fn join, B: IntoTransaction>( 4 | a: A, 5 | b: B, 6 | ) -> Join { 7 | Join { 8 | tx1: a.into_transaction(), 9 | tx2: b.into_transaction(), 10 | } 11 | 12 | } 13 | 14 | 15 | /// The result of `join` 16 | #[derive(Debug)] 17 | #[must_use] 18 | pub struct Join { 19 | tx1: Tx1, 20 | tx2: Tx2, 21 | } 22 | 23 | impl Transaction for Join 24 | where 25 | Tx1: Transaction, 26 | Tx2: Transaction, 27 | { 28 | type Ctx = Tx1::Ctx; 29 | type Item = (Tx1::Item, Tx2::Item); 30 | type Err = Tx1::Err; 31 | 32 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 33 | let &Join { ref tx1, ref tx2, .. } = self; 34 | match (tx1.run(ctx), tx2.run(ctx)) { 35 | (Ok(r1), Ok(r2)) => Ok((r1, r2)), 36 | (Err(e), _) | (_, Err(e)) => Err(e), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /transaction/src/join3.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | pub fn join3< 4 | Ctx, 5 | A: IntoTransaction, 6 | B: IntoTransaction, 7 | C: IntoTransaction, 8 | >( 9 | a: A, 10 | b: B, 11 | c: C, 12 | ) -> Join3 { 13 | Join3 { 14 | tx1: a.into_transaction(), 15 | tx2: b.into_transaction(), 16 | tx3: c.into_transaction(), 17 | } 18 | 19 | } 20 | 21 | /// The result of `join3` 22 | #[derive(Debug)] 23 | #[must_use] 24 | pub struct Join3 { 25 | tx1: Tx1, 26 | tx2: Tx2, 27 | tx3: Tx3, 28 | } 29 | 30 | impl Transaction for Join3 31 | where 32 | Tx1: Transaction, 33 | Tx2: Transaction< 34 | Ctx = Tx1::Ctx, 35 | Err = Tx1::Err, 36 | >, 37 | Tx3: Transaction< 38 | Ctx = Tx1::Ctx, 39 | Err = Tx1::Err, 40 | >, 41 | { 42 | type Ctx = Tx1::Ctx; 43 | type Item = (Tx1::Item, Tx2::Item, Tx3::Item); 44 | type Err = Tx1::Err; 45 | 46 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 47 | let &Join3 { 48 | ref tx1, 49 | ref tx2, 50 | ref tx3, 51 | } = self; 52 | match (tx1.run(ctx), tx2.run(ctx), tx3.run(ctx)) { 53 | (Ok(r1), Ok(r2), Ok(r3)) => Ok((r1, r2, r3)), 54 | (Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => Err(e), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /transaction/src/join4.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | pub fn join4< 4 | Ctx, 5 | A: IntoTransaction, 6 | B: IntoTransaction, 7 | C: IntoTransaction, 8 | D: IntoTransaction, 9 | >( 10 | a: A, 11 | b: B, 12 | c: C, 13 | d: D, 14 | ) -> Join4 { 15 | Join4 { 16 | tx1: a.into_transaction(), 17 | tx2: b.into_transaction(), 18 | tx3: c.into_transaction(), 19 | tx4: d.into_transaction(), 20 | } 21 | 22 | } 23 | 24 | /// The result of `join4` 25 | #[derive(Debug)] 26 | #[must_use] 27 | pub struct Join4 { 28 | tx1: Tx1, 29 | tx2: Tx2, 30 | tx3: Tx3, 31 | tx4: Tx4, 32 | } 33 | 34 | impl Transaction for Join4 35 | where 36 | Tx1: Transaction, 37 | Tx2: Transaction< 38 | Ctx = Tx1::Ctx, 39 | Err = Tx1::Err, 40 | >, 41 | Tx3: Transaction< 42 | Ctx = Tx1::Ctx, 43 | Err = Tx1::Err, 44 | >, 45 | Tx4: Transaction< 46 | Ctx = Tx1::Ctx, 47 | Err = Tx1::Err, 48 | >, 49 | { 50 | type Ctx = Tx1::Ctx; 51 | type Item = (Tx1::Item, Tx2::Item, Tx3::Item, Tx4::Item); 52 | type Err = Tx1::Err; 53 | 54 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 55 | let &Join4 { 56 | ref tx1, 57 | ref tx2, 58 | ref tx3, 59 | ref tx4, 60 | } = self; 61 | match (tx1.run(ctx), tx2.run(ctx), tx3.run(ctx), tx4.run(ctx)) { 62 | (Ok(r1), Ok(r2), Ok(r3), Ok(r4)) => Ok((r1, r2, r3, r4)), 63 | (Err(e), _, _, _) | 64 | (_, Err(e), _, _) | 65 | (_, _, Err(e), _) | 66 | (_, _, _, Err(e)) => Err(e), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /transaction/src/join_all.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | /// join a vec of transaction 4 | pub fn join_all(i: I) -> JoinAll 5 | where 6 | I: IntoIterator, 7 | B: IntoTransaction, 8 | { 9 | JoinAll { 10 | vec: i.into_iter() 11 | .map(IntoTransaction::into_transaction) 12 | .collect(), 13 | } 14 | } 15 | 16 | /// The result of `join_vec` 17 | #[derive(Debug)] 18 | #[must_use] 19 | pub struct JoinAll { 20 | vec: Vec, 21 | } 22 | 23 | impl Transaction for JoinAll 24 | where 25 | Tx: Transaction, 26 | { 27 | type Ctx = Tx::Ctx; 28 | type Item = Vec; 29 | type Err = Tx::Err; 30 | 31 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 32 | let vec = &self.vec; 33 | 34 | vec.iter() 35 | .map(|tx| tx.run(ctx)) 36 | .collect::, _>>() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /transaction/src/lazy.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use Transaction; 4 | 5 | /// lazy evaluated transaction value. 6 | /// Note that inner function can be called many times. 7 | pub fn lazy(f: F) -> Lazy 8 | where 9 | F: Fn() -> Result, 10 | { 11 | Lazy { 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | /// The result of `lazy` 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct Lazy { 21 | f: F, 22 | _phantom: PhantomData, 23 | } 24 | 25 | impl Transaction for Lazy 26 | where 27 | F: Fn() -> Result, 28 | { 29 | type Ctx = Ctx; 30 | type Item = T; 31 | type Err = E; 32 | fn run(&self, _ctx: &mut Self::Ctx) -> Result { 33 | (self.f)() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /transaction/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Zero-cost transaction abstraction in Rust 2 | //! This crate abstracts over transactions like STM, SQL transactions and so 3 | //! on. It is composable via combinators and does DI of transactions. 4 | //! 5 | //! 6 | //! The basic idea is representing contracts of "this computation must be run under a transaction" 7 | //! as types. The trait `Transaction` represents a sequence of computation that 8 | //! must be run under a transaction. And transactions are composable 9 | //! (sequencable) using `then`, `and_then`, `or_else`, hence you can use it 10 | //! like values wrapped in `Result`. Since it represents computation to be run 11 | //! in data, some types respond to control operators are provided: `abort` for 12 | //! `?`, `repeat` for `for`, `loop_fn` for `loop` and `branch` for (join point 13 | //! of) `if` and so on. As all the combinators have its own result type, no 14 | //! dispatches are done at execution time thus it is zero-cost. 15 | //! 16 | //! Another feature is it does DI of transaction. For database transaction, it 17 | //! means that it injects DB connection from the context. 18 | //! 19 | //! # Examples 20 | //! 21 | //! ``` 22 | //! 23 | //! extern crate transaction; 24 | //! 25 | //! use self::transaction::prelude::*; 26 | //! 27 | //! # struct FooConnection; 28 | //! # struct FooError; 29 | //! # #[derive(Clone)]struct User; 30 | //! 31 | //! // Since current rust doesn't support `impl Trait`, you need to make a 32 | //! // trait box 33 | //! // to return a trait value from a function. 34 | //! type BoxTx<'a, T> = Box 38 | //! + 'a>; 39 | //! 40 | //! fn find_user<'a>(id: i64) -> BoxTx<'a, Option> { 41 | //! // connection is inejected from the context 42 | //! with_ctx(move |cn: &mut FooConnection| { 43 | //! // .. 44 | //! # let _ = (id, cn); 45 | //! # unimplemented!() 46 | //! }).boxed() 47 | //! 48 | //! } 49 | //! 50 | //! fn update_user<'a>(id: i64, name: &'a str) -> BoxTx<'a, Option<()>> { 51 | //! with_ctx(move |cn: &mut FooConnection| { 52 | //! // .. 53 | //! # let _ = (id, cn, name); 54 | //! # unimplemented!() 55 | //! }).boxed() 56 | //! } 57 | //! 58 | //! fn update_find_user<'a>(id: i64, name: &'a str) -> BoxTx<'a, Option> { 59 | //! update_user(id, name) 60 | //! // transaction can be composed using `and_then` 61 | //! .and_then(move |ret| match ret { 62 | //! None => 63 | //! // to return a leaf transaction, use `ok`, `err` or `result` 64 | //! ok(None) 65 | //! // to return from a branch (or, to match types at join 66 | //! // point), use `branch` API 67 | //! .branch() 68 | //! // use `first` in the first arm of the brnach 69 | //! .first(), 70 | //! Some(()) => find_user(id) 71 | //! .branch() 72 | //! // use `second` in the second arm of the brnach 73 | //! .second(), 74 | //! }) 75 | //! // finally, box it to return `BoxTx`. 76 | //! .boxed() 77 | //! } 78 | //! # fn main() {} 79 | //! ``` 80 | 81 | 82 | #[cfg(feature = "mdo")] 83 | pub mod mdo; 84 | 85 | pub mod prelude { 86 | pub use super::Transaction; 87 | pub use err::err; 88 | pub use join_all::join_all; 89 | pub use lazy::lazy; 90 | pub use loop_fn::loop_fn; 91 | pub use ok::ok; 92 | pub use repeat::repeat; 93 | pub use result::result; 94 | pub use retry::retry; 95 | pub use with_ctx::with_ctx; 96 | } 97 | 98 | mod then; 99 | mod map; 100 | mod and_then; 101 | mod map_err; 102 | mod or_else; 103 | mod abort; 104 | mod try_abort; 105 | mod recover; 106 | mod try_recover; 107 | mod join; 108 | mod join3; 109 | mod join4; 110 | mod branch; 111 | mod branch3; 112 | mod branch4; 113 | mod loop_fn; 114 | mod repeat; 115 | mod retry; 116 | mod result; 117 | mod ok; 118 | mod err; 119 | mod lazy; 120 | mod join_all; 121 | mod with_ctx; 122 | 123 | pub use abort::*; 124 | pub use and_then::*; 125 | pub use branch::*; 126 | pub use branch3::*; 127 | pub use branch4::*; 128 | pub use err::*; 129 | pub use join::*; 130 | pub use join3::*; 131 | pub use join4::*; 132 | pub use join_all::*; 133 | pub use lazy::*; 134 | pub use loop_fn::*; 135 | pub use map::*; 136 | pub use map_err::*; 137 | pub use ok::*; 138 | pub use or_else::*; 139 | pub use recover::*; 140 | pub use repeat::*; 141 | pub use result::*; 142 | pub use retry::*; 143 | pub use then::*; 144 | pub use try_abort::*; 145 | pub use try_recover::*; 146 | pub use with_ctx::*; 147 | 148 | /// An abstract transaction. Transactions sharing the same `Ctx` can be 149 | /// composed with combinators. When the transaction return an error, it means 150 | /// the transaction is failed. Some runners may abort the transaction and the 151 | /// other may retry the computation. Thus all the computation should be 152 | /// idempotent (of cause, except operations using context). Note that this 153 | /// transaction is not executed until it is `run`. 154 | #[must_use] 155 | pub trait Transaction { 156 | /// The contxt type (i.e. transaction type) of the transaction 157 | type Ctx; 158 | /// The return type of the transaction 159 | type Item; 160 | /// The error type of the transaction 161 | type Err; 162 | 163 | /// Run the transaction. This will called by transaction runner rather than 164 | /// user by hand. 165 | fn run(&self, ctx: &mut Self::Ctx) -> Result; 166 | 167 | /// Box the transaction 168 | fn boxed<'a>(self) -> Box + 'a> 169 | where 170 | Self: Sized + 'a, 171 | { 172 | Box::new(self) 173 | } 174 | 175 | /// Take the previous result of computation and do another computation 176 | fn then(self, f: F) -> Then 177 | where 178 | Tx2: IntoTransaction, 179 | F: Fn(Result) -> Tx2, 180 | Self: Sized, 181 | { 182 | then(self, f) 183 | } 184 | 185 | /// Transform the previous successful value 186 | fn map(self, f: F) -> Map 187 | where 188 | F: Fn(Self::Item) -> B, 189 | Self: Sized, 190 | { 191 | map(self, f) 192 | } 193 | 194 | 195 | 196 | /// Take the previous successful value of computation and do another 197 | /// computation 198 | fn and_then(self, f: F) -> AndThen 199 | where 200 | B: IntoTransaction, 201 | F: Fn(Self::Item) -> B, 202 | Self: Sized, 203 | { 204 | and_then(self, f) 205 | } 206 | 207 | /// Transform the previous error value 208 | fn map_err(self, f: F) -> MapErr 209 | where 210 | F: Fn(Self::Err) -> B, 211 | Self: Sized, 212 | { 213 | map_err(self, f) 214 | } 215 | 216 | 217 | /// Take the previous error value of computation and do another computation. 218 | /// This may be used falling back 219 | fn or_else(self, f: F) -> OrElse 220 | where 221 | B: IntoTransaction, 222 | F: Fn(Self::Err) -> B, 223 | Self: Sized, 224 | { 225 | or_else(self, f) 226 | } 227 | 228 | /// Take the previous successfull value of computation and abort the 229 | /// transaction. 230 | fn abort(self, f: F) -> Abort 231 | where 232 | F: Fn(Self::Item) -> Self::Err, 233 | Self: Sized, 234 | { 235 | abort(self, f) 236 | } 237 | 238 | /// Try to abort the transaction 239 | fn try_abort(self, f: F) -> TryAbort 240 | where 241 | F: Fn(Self::Item) -> Result, 242 | Self: Sized, 243 | { 244 | try_abort(self, f) 245 | } 246 | 247 | /// Recover from an error 248 | fn recover(self, f: F) -> Recover 249 | where 250 | F: Fn(Self::Err) -> Self::Item, 251 | Self: Sized, 252 | { 253 | recover(self, f) 254 | } 255 | 256 | /// Try to recover from an error 257 | fn try_recover(self, f: F) -> TryRecover 258 | where 259 | F: Fn(Self::Err) -> Result, 260 | Self: Sized, 261 | { 262 | try_recover(self, f) 263 | } 264 | 265 | /// join 2 indepndant transactions 266 | fn join(self, b: B) -> Join 267 | where 268 | B: IntoTransaction, 269 | Self: Sized, 270 | { 271 | join(self, b) 272 | } 273 | 274 | /// join 3 indepndant transactions 275 | fn join3(self, b: B, c: C) -> Join3 276 | where 277 | B: IntoTransaction, 278 | C: IntoTransaction, 279 | Self: Sized, 280 | { 281 | join3(self, b, c) 282 | } 283 | 284 | /// join 4 indepndant transactions 285 | fn join4(self, b: B, c: C, d: D) -> Join4 286 | where 287 | B: IntoTransaction, 288 | C: IntoTransaction, 289 | D: IntoTransaction, 290 | Self: Sized, 291 | { 292 | join4(self, b, c, d) 293 | } 294 | 295 | /// branch builder 296 | fn branch(self) -> BranchBuilder 297 | where 298 | Self: Sized, 299 | { 300 | BranchBuilder::new(self) 301 | } 302 | 303 | /// 3 branch builder 304 | fn branch3(self) -> Branch3Builder 305 | where 306 | Self: Sized, 307 | { 308 | Branch3Builder::new(self) 309 | } 310 | 311 | /// 4 branch builder 312 | fn branch4(self) -> Branch4Builder 313 | where 314 | Self: Sized, 315 | { 316 | Branch4Builder::new(self) 317 | } 318 | } 319 | 320 | /// types than can be converted into transaction 321 | pub trait IntoTransaction { 322 | type Tx: Transaction; 323 | type Err; 324 | type Item; 325 | 326 | fn into_transaction(self) -> Self::Tx; 327 | } 328 | 329 | impl IntoTransaction for Tx 330 | where 331 | Tx: Transaction, 332 | { 333 | type Tx = Tx; 334 | type Err = Tx::Err; 335 | type Item = Tx::Item; 336 | 337 | fn into_transaction(self) -> Self::Tx { 338 | self 339 | } 340 | } 341 | 342 | impl IntoTransaction for (Tx1, Tx2) 343 | where 344 | Tx1: IntoTransaction, 345 | Tx2: IntoTransaction, 346 | { 347 | type Tx = Join; 348 | type Err = Tx1::Err; 349 | type Item = (Tx1::Item, Tx2::Item); 350 | fn into_transaction(self) -> Self::Tx { 351 | let (tx1, tx2) = self; 352 | tx1.into_transaction().join(tx2.into_transaction()) 353 | } 354 | } 355 | 356 | impl IntoTransaction for (Tx1, Tx2, Tx3) 357 | where 358 | Tx1: IntoTransaction, 359 | Tx2: IntoTransaction< 360 | Ctx, 361 | Err = Tx1::Err, 362 | >, 363 | Tx3: IntoTransaction< 364 | Ctx, 365 | Err = Tx1::Err, 366 | >, 367 | { 368 | type Tx = Join3; 369 | type Err = Tx1::Err; 370 | type Item = (Tx1::Item, Tx2::Item, Tx3::Item); 371 | fn into_transaction(self) -> Self::Tx { 372 | let (tx1, tx2, tx3) = self; 373 | tx1.into_transaction().join3( 374 | tx2.into_transaction(), 375 | tx3.into_transaction(), 376 | ) 377 | } 378 | } 379 | 380 | impl IntoTransaction for (Tx1, Tx2, Tx3, Tx4) 381 | where 382 | Tx1: IntoTransaction, 383 | Tx2: IntoTransaction< 384 | Ctx, 385 | Err = Tx1::Err, 386 | >, 387 | Tx3: IntoTransaction< 388 | Ctx, 389 | Err = Tx1::Err, 390 | >, 391 | Tx4: IntoTransaction< 392 | Ctx, 393 | Err = Tx1::Err, 394 | >, 395 | { 396 | type Tx = Join4; 397 | type Err = Tx1::Err; 398 | type Item = (Tx1::Item, Tx2::Item, Tx3::Item, Tx4::Item); 399 | fn into_transaction(self) -> Self::Tx { 400 | let (tx1, tx2, tx3, tx4) = self; 401 | tx1.into_transaction().join4( 402 | tx2.into_transaction(), 403 | tx3.into_transaction(), 404 | tx4.into_transaction(), 405 | ) 406 | } 407 | } 408 | 409 | impl IntoTransaction for Result 410 | where 411 | T: Clone, 412 | E: Clone, 413 | { 414 | type Tx = result::TxResult; 415 | type Err = E; 416 | type Item = T; 417 | 418 | fn into_transaction(self) -> Self::Tx { 419 | result::result(self) 420 | } 421 | } 422 | 423 | impl Transaction for Fn(&mut Ctx) -> Result { 424 | type Ctx = Ctx; 425 | type Item = T; 426 | type Err = E; 427 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 428 | self(ctx) 429 | } 430 | } 431 | 432 | 433 | impl Transaction for Box 434 | where 435 | T: ?Sized + Transaction, 436 | { 437 | type Ctx = T::Ctx; 438 | type Item = T::Item; 439 | type Err = T::Err; 440 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 441 | (**self).run(ctx) 442 | } 443 | } 444 | 445 | impl<'a, T> Transaction for &'a T 446 | where 447 | T: ?Sized + Transaction, 448 | { 449 | type Ctx = T::Ctx; 450 | type Item = T::Item; 451 | type Err = T::Err; 452 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 453 | (**self).run(ctx) 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /transaction/src/loop_fn.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn loop_fn(initial_state: S, f: F) -> LoopFn 6 | where 7 | A: IntoTransaction>, 8 | F: Fn(S) -> A, 9 | { 10 | LoopFn { 11 | tx: f(initial_state).into_transaction(), 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | /// The result of `loop_fn` 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct LoopFn> { 21 | tx: A::Tx, 22 | f: F, 23 | _phantom: PhantomData<(Ctx)>, 24 | } 25 | 26 | /// The status of a `loop_fn` loop. 27 | #[derive(Debug)] 28 | pub enum Loop { 29 | /// Indicates that the loop has completed with output `T`. 30 | Break(T), 31 | /// Indicates that the loop function should be called again with input state `S`. 32 | Continue(S), 33 | } 34 | 35 | impl Transaction for LoopFn 36 | where 37 | F: Fn(S) -> A, 38 | A: IntoTransaction>, 39 | { 40 | type Ctx = Ctx; 41 | type Item = T; 42 | type Err = A::Err; 43 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 44 | let LoopFn { ref tx, ref f, .. } = *self; 45 | let mut ret = tx.run(ctx)?; 46 | loop { 47 | let s = match ret { 48 | Loop::Break(t) => return Ok(t), 49 | Loop::Continue(s) => s, 50 | }; 51 | ret = f(s).into_transaction().run(ctx)?; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /transaction/src/map.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | pub fn map(a: A, f: F) -> Map 4 | where 5 | A: IntoTransaction, 6 | F: Fn(A::Item) -> B, 7 | { 8 | Map { 9 | tx: a.into_transaction(), 10 | f: f, 11 | } 12 | } 13 | 14 | 15 | 16 | /// The result of `map` 17 | #[derive(Debug)] 18 | #[must_use] 19 | pub struct Map { 20 | tx: Tx, 21 | f: F, 22 | } 23 | impl Transaction for Map 24 | where 25 | Tx: Transaction, 26 | F: Fn(Tx::Item) -> U, 27 | { 28 | type Ctx = Tx::Ctx; 29 | type Item = U; 30 | type Err = Tx::Err; 31 | 32 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 33 | let &Map { ref tx, ref f } = self; 34 | tx.run(ctx).map(f) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /transaction/src/map_err.rs: -------------------------------------------------------------------------------- 1 | use {IntoTransaction, Transaction}; 2 | 3 | pub fn map_err(a: A, f: F) -> MapErr 4 | where 5 | A: IntoTransaction, 6 | F: Fn(A::Err) -> B, 7 | { 8 | MapErr { 9 | tx: a.into_transaction(), 10 | f: f, 11 | } 12 | } 13 | 14 | 15 | /// The result of `map_err` 16 | #[derive(Debug)] 17 | #[must_use] 18 | pub struct MapErr { 19 | tx: Tx, 20 | f: F, 21 | } 22 | 23 | impl Transaction for MapErr 24 | where 25 | Tx: Transaction, 26 | F: Fn(Tx::Err) -> E, 27 | { 28 | type Ctx = Tx::Ctx; 29 | type Item = Tx::Item; 30 | type Err = E; 31 | 32 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 33 | let &MapErr { ref tx, ref f } = self; 34 | tx.run(ctx).map_err(f) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /transaction/src/mdo.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// bind for Transaction>, equivalent to `tx.and_then(f) 4 | pub fn bind(tx: Tx, f: F) -> ::AndThen 5 | where 6 | B: Transaction, 7 | F: Fn(Tx::Item) -> B, 8 | Tx: Transaction + Sized, 9 | { 10 | tx.and_then(f) 11 | } 12 | 13 | /// return for Transaction, equivalent to `ok(x)` 14 | pub fn ret(x: T) -> ::TxOk { 15 | ok(x) 16 | } 17 | -------------------------------------------------------------------------------- /transaction/src/ok.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use Transaction; 4 | 5 | /// make a successful transaction value. 6 | pub fn ok(t: T) -> TxOk { 7 | TxOk { 8 | ok: t, 9 | _phantom: PhantomData, 10 | } 11 | } 12 | 13 | /// The result of `ok` 14 | #[derive(Debug)] 15 | #[must_use] 16 | pub struct TxOk { 17 | ok: T, 18 | _phantom: PhantomData<(Ctx, E)>, 19 | } 20 | 21 | impl Transaction for TxOk 22 | where 23 | T: Clone, 24 | { 25 | type Ctx = Ctx; 26 | type Item = T; 27 | type Err = E; 28 | fn run(&self, _ctx: &mut Self::Ctx) -> Result { 29 | Ok(self.ok.clone()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /transaction/src/or_else.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | 6 | pub fn or_else(a: A, f: F) -> OrElse 7 | where 8 | A: IntoTransaction, 9 | B: IntoTransaction, 10 | F: Fn(A::Err) -> B, 11 | { 12 | OrElse { 13 | tx: a.into_transaction(), 14 | f: f, 15 | _phantom: PhantomData, 16 | } 17 | } 18 | 19 | 20 | /// The result of `or_else` 21 | #[derive(Debug)] 22 | #[must_use] 23 | pub struct OrElse { 24 | tx: Tx1, 25 | f: F, 26 | _phantom: PhantomData, 27 | } 28 | 29 | impl Transaction for OrElse 30 | where 31 | Tx2: IntoTransaction< 32 | Tx::Ctx, 33 | Item = Tx::Item, 34 | Err = Tx::Err, 35 | >, 36 | Tx: Transaction, 37 | F: Fn(Tx::Err) -> Tx2, 38 | { 39 | type Ctx = Tx::Ctx; 40 | type Item = Tx::Item; 41 | type Err = Tx::Err; 42 | 43 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 44 | let &OrElse { ref tx, ref f, .. } = self; 45 | tx.run(ctx).or_else( 46 | |item| f(item).into_transaction().run(ctx), 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /transaction/src/recover.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn recover(a: A, f: F) -> Recover 6 | where 7 | A: IntoTransaction, 8 | F: Fn(A::Err) -> A::Item, 9 | { 10 | Recover { 11 | tx: a.into_transaction(), 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | /// The result of `recover` 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct Recover { 21 | tx: Tx, 22 | f: F, 23 | _phantom: PhantomData, 24 | } 25 | 26 | impl Transaction for Recover 27 | where 28 | Tx: Transaction, 29 | F: Fn(Tx::Err) -> Tx::Item, 30 | { 31 | type Ctx = Tx::Ctx; 32 | type Item = Tx::Item; 33 | type Err = Tx::Err; 34 | 35 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 36 | let &Recover { ref tx, ref f, .. } = self; 37 | match tx.run(ctx) { 38 | r @ Ok(_) => r, 39 | Err(e) => Ok(f(e)), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /transaction/src/repeat.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn repeat(n: usize, f: F) -> Repeat 6 | where 7 | Tx: IntoTransaction, 8 | F: Fn(usize) -> Tx, 9 | { 10 | Repeat { 11 | n: n, 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | /// The result of `repeat` 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct Repeat { 21 | n: usize, 22 | f: F, 23 | _phantom: PhantomData<(Tx, Ctx)>, 24 | } 25 | 26 | impl Transaction for Repeat 27 | where 28 | F: Fn(usize) -> Tx, 29 | Tx: IntoTransaction, 30 | { 31 | type Ctx = Ctx; 32 | type Item = Vec; 33 | type Err = Tx::Err; 34 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 35 | let Repeat { ref n, ref f, .. } = *self; 36 | let mut ret = Vec::new(); 37 | for i in 0..*n { 38 | let t = f(i).into_transaction().run(ctx)?; 39 | ret.push(t); 40 | } 41 | Ok(ret) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /transaction/src/result.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use Transaction; 4 | 5 | /// The result of `result` 6 | #[derive(Debug)] 7 | #[must_use] 8 | pub struct TxResult { 9 | r: Result, 10 | _phantom: PhantomData, 11 | } 12 | 13 | /// Take a result and make a leaf transaction value. 14 | pub fn result(r: Result) -> TxResult { 15 | TxResult { 16 | r: r, 17 | _phantom: PhantomData, 18 | } 19 | } 20 | 21 | impl Transaction for TxResult 22 | where 23 | T: Clone, 24 | E: Clone, 25 | { 26 | type Ctx = Ctx; 27 | type Item = T; 28 | type Err = E; 29 | fn run(&self, _ctx: &mut Self::Ctx) -> Result { 30 | self.r.clone() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /transaction/src/retry.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | 6 | 7 | pub fn retry(n: usize, f: F) -> Retry 8 | where 9 | Tx: IntoTransaction, 10 | F: Fn(usize) -> Tx, 11 | { 12 | Retry { 13 | n: n, 14 | f: f, 15 | _phantom: PhantomData, 16 | } 17 | } 18 | 19 | /// The result of `retry` 20 | #[derive(Debug)] 21 | #[must_use] 22 | pub struct Retry { 23 | n: usize, 24 | f: F, 25 | _phantom: PhantomData<(Tx, Ctx)>, 26 | } 27 | 28 | impl Transaction for Retry 29 | where 30 | F: Fn(usize) -> Tx, 31 | Tx: IntoTransaction, 32 | { 33 | type Ctx = Ctx; 34 | type Item = Tx::Item; 35 | type Err = Vec; 36 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 37 | let Retry { ref n, ref f, .. } = *self; 38 | let mut ret = Vec::new(); 39 | for i in 0..*n { 40 | let t = match f(i).into_transaction().run(ctx) { 41 | Ok(t) => return Ok(t), 42 | Err(e) => e, 43 | }; 44 | ret.push(t); 45 | } 46 | Err(ret) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /transaction/src/then.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn then(a: A, f: F) -> Then 6 | where 7 | A: IntoTransaction, 8 | Tx2: IntoTransaction, 9 | F: Fn(Result) -> Tx2, 10 | { 11 | Then { 12 | tx: a.into_transaction(), 13 | f: f, 14 | _phantom: PhantomData, 15 | } 16 | } 17 | 18 | /// The result of `then` 19 | #[derive(Debug)] 20 | #[must_use] 21 | pub struct Then { 22 | tx: Tx1, 23 | f: F, 24 | _phantom: PhantomData, 25 | } 26 | 27 | impl Transaction for Then 28 | where 29 | Tx2: IntoTransaction, 30 | Tx: Transaction, 31 | F: Fn(Result) -> Tx2, 32 | { 33 | type Ctx = Tx::Ctx; 34 | type Item = Tx2::Item; 35 | type Err = Tx2::Err; 36 | 37 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 38 | let &Then { ref tx, ref f, .. } = self; 39 | f(tx.run(ctx)).into_transaction().run(ctx) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /transaction/src/try_abort.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn try_abort(a: A, f: F) -> TryAbort 6 | where 7 | A: IntoTransaction, 8 | F: Fn(A::Item) -> Result, 9 | { 10 | TryAbort { 11 | tx: a.into_transaction(), 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct TryAbort { 21 | tx: Tx, 22 | f: F, 23 | _phantom: PhantomData, 24 | } 25 | 26 | impl Transaction for TryAbort 27 | where 28 | Tx: Transaction, 29 | F: Fn(Tx::Item) -> Result, 30 | { 31 | type Ctx = Tx::Ctx; 32 | type Item = B; 33 | type Err = Tx::Err; 34 | 35 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 36 | let TryAbort { ref tx, ref f, .. } = *self; 37 | match tx.run(ctx) { 38 | Ok(r) => f(r), 39 | Err(e) => Err(e), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /transaction/src/try_recover.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use {IntoTransaction, Transaction}; 4 | 5 | pub fn try_recover(a: A, f: F) -> TryRecover 6 | where 7 | A: IntoTransaction, 8 | F: Fn(A::Err) -> Result, 9 | { 10 | TryRecover { 11 | tx: a.into_transaction(), 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | 16 | } 17 | 18 | /// The result of `try_recover` 19 | #[derive(Debug)] 20 | #[must_use] 21 | pub struct TryRecover { 22 | tx: Tx, 23 | f: F, 24 | _phantom: PhantomData, 25 | } 26 | 27 | impl Transaction for TryRecover 28 | where 29 | Tx: Transaction, 30 | F: Fn(Tx::Err) -> Result, 31 | { 32 | type Ctx = Tx::Ctx; 33 | type Item = Tx::Item; 34 | type Err = B; 35 | 36 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 37 | let TryRecover { ref tx, ref f, .. } = *self; 38 | match tx.run(ctx) { 39 | Ok(r) => Ok(r), 40 | Err(e) => f(e), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /transaction/src/with_ctx.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use Transaction; 4 | 5 | 6 | /// Receive the context from the executing transaction and perform computation. 7 | pub fn with_ctx(f: F) -> WithCtx 8 | where 9 | F: Fn(&mut Ctx) -> Result, 10 | { 11 | WithCtx { 12 | f: f, 13 | _phantom: PhantomData, 14 | } 15 | } 16 | 17 | /// The result of `with_ctx` 18 | #[derive(Debug)] 19 | #[must_use] 20 | pub struct WithCtx { 21 | f: F, 22 | _phantom: PhantomData, 23 | } 24 | 25 | impl Transaction for WithCtx 26 | where 27 | F: Fn(&mut Ctx) -> Result, 28 | { 29 | type Ctx = Ctx; 30 | type Item = T; 31 | type Err = E; 32 | fn run(&self, ctx: &mut Self::Ctx) -> Result { 33 | (self.f)(ctx) 34 | } 35 | } 36 | --------------------------------------------------------------------------------