1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
//! A (not yet complete) implementation of a SlimServer for acceptance testing.
//!
//! This was implementating using the documentation found [here](http://fitnesse.org/FitNesse.UserGuide.WritingAcceptanceTests.SliM.SlimProtocol)
//!
//! To add it to your project simply run:
//! ```bash
//! cargo add rust_slim -F macros --dev
//! ```
//!
//! Then you need to create your fixtures. The recomended way of doing this is by using the `#[fixture]` macro. Here is an example:
//!
//! ```rust
//! use rust_slim::fixture;
//!
//! #[derive(Default)]
//! pub struct Calculator {
//! a: i64,
//! b: i64,
//! }
//!
//! #[fixture]
//! impl Calculator {
//! pub fn set_a(&mut self, a: i64) {
//! self.a = a
//! }
//!
//! pub fn set_b(&mut self, b: i64) {
//! self.b = b
//! }
//!
//! pub fn sum(&self) -> i64 {
//! self.a + self.b
//! }
//!
//! pub fn mul(&self) -> i64 {
//! self.a * self.b
//! }
//!
//! pub fn sub(&self) -> i64 {
//! self.a - self.b
//! }
//!
//! pub fn div(&self) -> i64 {
//! self.a / self.b
//! }
//! }
//! ```
//! All methods that are public will be able to be called by the slim server.
//!
//! Than, we need to add an entrypoint to the slim server so we can run it. There are lot of ways of doing this. One is by creating an example in your project.
//!
//! So create and example file called `calculator.rs` and add this:
//! ```rust
//! use rust_slim::SlimServer;
//! use std::net::TcpListener;
//! use anyhow::Result;
//! use std::env::args;
//! # use std::net::TcpStream;
//! # use std::thread::spawn;
//! # use std::io::Write;
//! # use rust_slim::fixture;
//!
//! # #[derive(Default)]
//! # pub struct Calculator {
//! # a: i64,
//! # b: i64,
//! # }
//! #
//! # #[fixture]
//! # impl Calculator {
//! # pub fn set_a(&mut self, a: i64) {
//! # self.a = a
//! # }
//! #
//! # pub fn set_b(&mut self, b: i64) {
//! # self.b = b
//! # }
//! #
//! # pub fn sum(&self) -> i64 {
//! # self.a + self.b
//! # }
//! #
//! # pub fn mul(&self) -> i64 {
//! # self.a * self.b
//! # }
//! #
//! # pub fn sub(&self) -> i64 {
//! # self.a - self.b
//! # }
//! #
//! # pub fn div(&self) -> i64 {
//! # self.a / self.b
//! # }
//! # }
//! #
//! fn main() -> Result<()> {
//! # spawn(|| {
//! # loop {
//! # if let Ok(mut stream) = TcpStream::connect("127.0.0.1:8085") {
//! # stream.write_all(b"000003:bye").unwrap();
//! # break;
//! # }
//! # }
//! # });
//! let port = args().skip(1).next().unwrap_or("8085".to_string());
//! let listener = TcpListener::bind(format!("0.0.0.0:{port}").to_string()).expect("Error");
//! let (stream, _) = listener.accept()?;
//! let mut server = SlimServer::new(stream.try_clone()?, stream);
//! server.add_fixture::<Calculator>();
//! server.run()?;
//! Ok(())
//! }
//! ```
//! Than, to run it, you simply call `cargo run --example calculator`. Now you need to configure your test runner ([fitnesse](https://fitnesse.org/), [temoc](https://github.com/killertux/temoc/tree/master/temoc)) to call your server
#[cfg(feature = "macros")]
pub use rust_slim_macros::*;
pub use server::SlimServer;
pub use to_slim_result_string::*;
pub use utils::from_rust_module_path_to_class_path;
mod server;
mod to_slim_result_string;
mod utils;
/// Fixtures must implement this trait to be able to be executed by the slim server.
/// The `#[fixture]` macro will automatically implement it for the type in the impl block.
pub trait SlimFixture {
/// Execute a method if it exists in the current fixture.
/// The `method`is the method name that should be executed.
fn execute_method(
&mut self,
method: &str,
args: Vec<String>,
) -> Result<String, ExecuteMethodError>;
}
/// ClassPath that will be used in the construction of the fixture.
/// It must be pascal case and have its parts separated by a `.`. Eg: `Fixtures.Calculator`
/// The `#[fixture]` macro will automatically implement it for the type in the impl block.
/// By default, the macro will get the current module path and add the fixutre type. For example, a fixutre with the type `Calculator` inside the module `examples::calculator::fixtures` will be converted to a path like `Examples.Calculator.Fixtures.Calculator`.
/// You can use a custom path by passing it to the macro as such
/// ```
/// use rust_slim::fixture;
/// #[derive(Default)]
/// struct Fixture {}
///
/// #[fixture("AnotherPath.MyFixutre")]
/// impl Fixture {}
/// ```
pub trait ClassPath {
fn class_path() -> String;
}
/// Trait used to construct the fixture. It is auto-implemented for fixtures that also implement Default.
pub trait Constructor {
fn construct(args: Vec<String>) -> Self;
}
impl<T> Constructor for T
where
T: Default,
{
fn construct(_args: Vec<String>) -> T {
T::default()
}
}
/// Error that can happen while trying to execute a method in a feature.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ExecuteMethodError {
/// The method might not exists, which should cause a MethodNotFound error.
MethodNotFound { method: String, class: String },
/// We might have an issue parsing the arguments. The implementation made by the `#[fixture]` macro tries to parse each argument using the [FromStr](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait.
ArgumentParsingError(String),
/// And there might be some failure in the method itself, which should cause an ExecutionError.
ExecutionError(String),
}
impl ToString for ExecuteMethodError {
fn to_string(&self) -> String {
match self {
ExecuteMethodError::MethodNotFound { method, class } => {
format!("NO_METHOD_IN_CLASS {method} {class}")
}
ExecuteMethodError::ArgumentParsingError(argument) => {
format!("NO_CONVERTER_FOR_ARGUMENT_NUMBER {argument}")
}
ExecuteMethodError::ExecutionError(error) => error.to_string(),
}
}
}