From e75b8f176cb5ea56a80683062e995169596d6a7c Mon Sep 17 00:00:00 2001 From: Shun Kashiwa Date: Sun, 1 Oct 2023 05:02:41 -0700 Subject: [PATCH] refactor `TransportConfig` with a separate builder struct --- chorus_book/src/guide-transport.md | 23 +++++---- chorus_lib/examples/tic-tac-toe.rs | 13 ++--- chorus_lib/src/transport.rs | 76 ++++++++++++++++++++++-------- chorus_lib/src/transport/http.rs | 51 +++++++++++++++----- 4 files changed, 115 insertions(+), 48 deletions(-) diff --git a/chorus_book/src/guide-transport.md b/chorus_book/src/guide-transport.md index 085db99..82ed2ff 100644 --- a/chorus_book/src/guide-transport.md +++ b/chorus_book/src/guide-transport.md @@ -87,21 +87,23 @@ let mut handles: Vec> = Vec::new(); The `http` transport is used to execute choreographies on different machines. This is useful for executing choreographies in a distributed system. -To use the `http` transport, import the `HttpTransport` struct and the `HttpTransportConfig` type alias from the `chorus_lib` crate. +To use the `http` transport, import `HttpTransport` and `HttpTransportConfigBuilder` from the `chorus_lib` crate. ```rust # extern crate chorus_lib; -use chorus_lib::transport::http::{HttpTransport, HttpTransportConfig}; +use chorus_lib::transport::http::{HttpTransport, HttpTransportConfigBuilder}; ``` -The primary constructor requires an argument of type `HttpTransportConfig`. To create an instance of this configuration, start with `HttpTransportConfig::for_target(target_location, (hostname, port))`. It will create set a projection target and the hostname and port to listen on. Then, provide information to connect to other locations by method-chaining the `.with(other_location, (hostname, port))` method. You can think of `HttpTransportConfig` as a mapping from locations to their hostnames and ports. +We need to construct a `HttpTransportConfig` using the `HttpTransportConfigBuilder`. First, we specify the target location and the hostname and port to listen on using the `for_target` method. Then, we specify the other locations and their `(hostname, port)` pairs using the `with` method. ```rust {{#include ./header.txt}} -# use chorus_lib::transport::http::{HttpTransport, HttpTransportConfig}; -let config = HttpTransportConfig::for_target(Alice, ("localhost".to_string(), 8080)) - .with(Bob, ("localhost".to_string(), 8081)); - +# use chorus_lib::transport::http::{HttpTransport, HttpTransportConfigBuilder}; +// `Alice` listens on port 8080 on localhost +let config = HttpTransportConfigBuilder::for_target(Alice, ("localhost".to_string(), 8080)) + // Connect to `Bob` on port 8081 on localhost + .with(Bob, ("localhost".to_string(), 8081)) + .build(); let transport = HttpTransport::new(config); ``` @@ -113,10 +115,11 @@ You can also create your own transport by implementing the `Transport` trait. It ```rust {{#include ./header.txt}} -# use chorus_lib::transport::TransportConfig; -let config = TransportConfig::for_target(Alice, ()) +# use chorus_lib::transport::TransportConfigBuilder; +let config = TransportConfigBuilder::for_target(Alice, ()) .with(Bob, ("localhost".to_string(), 8081)) - .with(Carol, ("localhost".to_string(), 8082)); + .with(Carol, ("localhost".to_string(), 8082)) + .build(); ``` See the API documentation for more details. diff --git a/chorus_lib/examples/tic-tac-toe.rs b/chorus_lib/examples/tic-tac-toe.rs index 6a09302..3cc4518 100644 --- a/chorus_lib/examples/tic-tac-toe.rs +++ b/chorus_lib/examples/tic-tac-toe.rs @@ -1,13 +1,12 @@ /// Choreographic tik-tak-toe game extern crate chorus_lib; -use chorus_lib::transport::http::HttpTransportConfig; use chorus_lib::{ core::{ ChoreoOp, Choreography, ChoreographyLocation, Deserialize, Located, LocationSet, Projector, Serialize, }, - transport::http::HttpTransport, + transport::http::{HttpTransport, HttpTransportConfigBuilder}, }; use clap::Parser; @@ -295,7 +294,7 @@ fn main() { match args.player { 'X' => { - let config = HttpTransportConfig::for_target( + let config = HttpTransportConfigBuilder::for_target( PlayerX, (args.hostname.as_str().to_string(), args.port), ) @@ -305,7 +304,8 @@ fn main() { args.opponent_hostname.as_str().to_string(), args.opponent_port, ), - ); + ) + .build(); let transport = HttpTransport::new(config); let projector = Projector::new(PlayerX, transport); @@ -315,7 +315,7 @@ fn main() { }); } 'O' => { - let config = HttpTransportConfig::for_target( + let config = HttpTransportConfigBuilder::for_target( PlayerO, (args.hostname.as_str().to_string(), args.port), ) @@ -325,7 +325,8 @@ fn main() { args.opponent_hostname.as_str().to_string(), args.opponent_port, ), - ); + ) + .build(); let transport = HttpTransport::new(config); let projector = Projector::new(PlayerO, transport); diff --git a/chorus_lib/src/transport.rs b/chorus_lib/src/transport.rs index 6d98d8c..ee7c666 100644 --- a/chorus_lib/src/transport.rs +++ b/chorus_lib/src/transport.rs @@ -9,44 +9,80 @@ use std::marker::PhantomData; /// A generic struct for configuration of `Transport`. #[derive(Clone)] -pub struct TransportConfig -{ +pub struct TransportConfig { /// The information about locations - info: HashMap, + info: HashMap, /// The information about the target choreography - target_info: (TargetLocation, TargetInfoType), + target_info: (Target, TargetInfo), /// The struct is parametrized by the location set (`L`). location_set: PhantomData, } -impl - TransportConfig +/// A builder for `TransportConfig`. +/// +/// Use this builder to create a `TransportConfig` instance. +/// +/// # Examples +/// +/// ``` +/// use chorus_lib::core::{LocationSet, ChoreographyLocation}; +/// use chorus_lib::transport::TransportConfigBuilder; +/// +/// #[derive(ChoreographyLocation)] +/// struct Alice; +/// +/// #[derive(ChoreographyLocation)] +/// struct Bob; +/// +/// let transport_config = TransportConfigBuilder::for_target(Alice, "value_for_target".to_string()) +/// .with(Bob, "value_for_bob".to_string()) +/// .build(); +/// ``` +pub struct TransportConfigBuilder { + target: (Target, TargetInfo), + location_set: PhantomData, + info: HashMap<&'static str, Info>, +} + +impl + TransportConfigBuilder { - /// A transport for a given target. - pub fn for_target(location: TargetLocation, info: TargetInfoType) -> Self { + /// Creates a new `TransportConfigBuilder` instance for a given target. + pub fn for_target(target: Target, info: TargetInfo) -> Self { Self { - info: HashMap::new(), - target_info: (location, info), + target: (target, info), location_set: PhantomData, + info: HashMap::new(), } } } -impl - TransportConfig +impl + TransportConfigBuilder { /// Adds information about a new `ChoreographyLocation`. + /// + /// This method tells the builder that the choreography involves a new location and how to communicate with it. pub fn with( - mut self, - _location: NewLocation, - info: InfoType, - ) -> TransportConfig, InfoType, TargetLocation, TargetInfoType> -where { - self.info.insert(NewLocation::name().to_string(), info); + self, + location: NewLocation, + info: Info, + ) -> TransportConfigBuilder, Info> { + _ = location; + let mut new_info = self.info; + new_info.insert(NewLocation::name(), info); + TransportConfigBuilder { + target: self.target, + location_set: PhantomData, + info: new_info, + } + } + /// Builds a `TransportConfig` instance. + pub fn build(self) -> TransportConfig { TransportConfig { - info: self.info, - target_info: self.target_info, + info: HashMap::new(), + target_info: self.target, location_set: PhantomData, } } diff --git a/chorus_lib/src/transport/http.rs b/chorus_lib/src/transport/http.rs index 0c20a58..bb019ea 100644 --- a/chorus_lib/src/transport/http.rs +++ b/chorus_lib/src/transport/http.rs @@ -12,16 +12,37 @@ use retry::{ use tiny_http::Server; use ureq::{Agent, AgentBuilder}; -use crate::transport::TransportConfig; - use crate::{ core::{ChoreographyLocation, HList, Member, Portable, Transport}, + transport::{TransportConfig, TransportConfigBuilder}, utils::queue::BlockingQueue, }; type QueueMap = HashMap>; -/// A type alias for `TransportConfig`s used for building `HttpTransport` -pub type HttpTransportConfig = TransportConfig; + +/// Config for `HttpTransport`. +pub type HttpTransportConfig = TransportConfig; + +/// A builder for `HttpTransportConfig`. +/// +/// # Examples +/// +/// ``` +/// # use chorus_lib::core::{LocationSet, ChoreographyLocation}; +/// # use chorus_lib::transport::http::HttpTransportConfigBuilder; +/// # +/// # #[derive(ChoreographyLocation)] +/// # struct Alice; +/// # +/// # #[derive(ChoreographyLocation)] +/// # struct Bob; +/// # +/// let transport_config = HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9010)) +/// .with(Bob, ("example.com".to_string(), 80)) +/// .build(); +/// ``` +pub type HttpTransportConfigBuilder = + TransportConfigBuilder; /// The header name for the source location. const HEADER_SRC: &str = "X-CHORUS-SOURCE"; @@ -155,8 +176,10 @@ mod tests { let mut handles = Vec::new(); { - let config = HttpTransportConfig::for_target(Alice, ("0.0.0.0".to_string(), 9010)) - .with(Bob, ("localhost".to_string(), 9011)); + let config = + HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9010)) + .with(Bob, ("localhost".to_string(), 9011)) + .build(); handles.push(thread::spawn(move || { wait.recv().unwrap(); // wait for Bob to start @@ -165,8 +188,9 @@ mod tests { })); } { - let config = HttpTransportConfig::for_target(Bob, ("0.0.0.0".to_string(), 9011)) - .with(Alice, ("localhost".to_string(), 9010)); + let config = HttpTransportConfigBuilder::for_target(Bob, ("0.0.0.0".to_string(), 9011)) + .with(Alice, ("localhost".to_string(), 9010)) + .build(); handles.push(thread::spawn(move || { let transport = HttpTransport::new(config); @@ -187,8 +211,10 @@ mod tests { let mut handles = Vec::new(); { - let config = HttpTransportConfig::for_target(Alice, ("0.0.0.0".to_string(), 9020)) - .with(Bob, ("localhost".to_string(), 9021)); + let config = + HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9020)) + .with(Bob, ("localhost".to_string(), 9021)) + .build(); handles.push(thread::spawn(move || { signal.send(()).unwrap(); @@ -197,8 +223,9 @@ mod tests { })); } { - let config = HttpTransportConfig::for_target(Bob, ("0.0.0.0".to_string(), 9021)) - .with(Alice, ("localhost".to_string(), 9020)); + let config = HttpTransportConfigBuilder::for_target(Bob, ("0.0.0.0".to_string(), 9021)) + .with(Alice, ("localhost".to_string(), 9020)) + .build(); handles.push(thread::spawn(move || { // wait for Alice to start, which forces Alice to retry