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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
//! # Getting started //! //! Let's start with a simple application. Let's write an application for //! greeting our users with a nice message. //! //! ``` //! use std::io::stdin; //! //! fn main() { //! println!("What is your name?"); //! let mut name = String::new(); //! stdin().read_line(&mut name).unwrap(); //! println!("Hello, {}! I hope you're having a wonderful day.", name); //! } //! ``` //! //! Cool! We can now ask our user for their name and greet them! But what if we //! want the user to be able to specify the name directly via command-line? //! //! ``` //! use std::{env::args, io::stdin}; //! //! fn get_name() -> String { //! let name_parts: Vec<_> = args().skip(1).collect(); //! if name_parts.is_empty() { //! println!("What is your name?"); //! let mut name = String::new(); //! stdin().read_line(&mut name).unwrap(); //! name //! } else { //! name_parts.join(" ") //! } //! } //! //! fn main() { //! let name = get_name(); //! println!("Hello, {}! I hope you're having a wonderful day.", name); //! } //! ``` //! //! Now our application lets users pass in their names directly, or if they //! don't, we can prompt them to provide it to us! This is cool, but we can //! still go further. Let's write some unit tests for our application so we can //! make sure our code does what it's supposed to do, even if we change it in //! the future. But wait, how do we verify that the output is correct? Well, //! we need a way to check the output of our program somehow! Let's write an //! abstraction for our output so we can check it it in our unit tests. //! //! ``` //! #[cfg(test)] //! # mod _ignored0 {} //! use std::fmt::Write; //! use std::{env::args, io::stdin}; //! //! trait OutputWriter { //! fn write_output(&mut self, message: &str); //! } //! //! struct ConsoleWriter; //! impl OutputWriter for ConsoleWriter { //! fn write_output(&mut self, message: &str) { //! println!("{}", message); //! } //! } //! //! // Our mock writer so we can observe the output in tests //! #[cfg(test)] //! # mod _ignored1 {} //! struct MockWriter(pub String); //! #[cfg(test)] //! # mod _ignored2 {} //! impl OutputWriter for MockWriter { //! fn write_output(&mut self, message: &str) { //! writeln!(self.0, "{}", message).unwrap(); //! } //! } //! //! trait InputReader { //! fn read_line(&mut self) -> String; //! } //! //! struct ConsoleReader; //! impl InputReader for ConsoleReader { //! fn read_line(&mut self) -> String { //! let mut input = String::new(); //! stdin().read_line(&mut input).unwrap(); //! input //! } //! } //! //! // Our mock reader for testing our application! //! #[cfg(test)] //! # mod _ignored3 {} //! struct MockReader(pub Option<String>); //! #[cfg(test)] //! # mod _ignored4 {} //! impl InputReader for MockReader { //! fn read_line(&mut self) -> String { //! self.0.take().unwrap() //! } //! } //! //! fn get_name<R: InputReader, W: OutputWriter>( //! args: &[String], //! reader: &mut R, //! writer: &mut W, //! ) -> String { //! let name_parts: Vec<&str> = //! args.iter().skip(1).map(|s| s.as_str()).collect(); //! if name_parts.is_empty() { //! writer.write_output("What is your name?"); //! reader.read_line() //! } else { //! name_parts.join(" ") //! } //! } //! //! fn main() { //! # // Let's actually verify the test passes //! # tests::name_is_correct(); //! # //! let args: Vec<_> = args().collect(); //! let mut reader = ConsoleReader; //! let mut writer = ConsoleWriter; //! let name = get_name(&args, &mut reader, &mut writer); //! writer.write_output(&format!( //! "Hello, {}! I hope you're having a wonderful day.", //! name //! )); //! } //! //! // Verify our program works like we want it to //! #[cfg(test)] //! # mod _ignored5 {} //! mod tests { //! use super::*; //! //! // Let's make sure we're getting the correct name from the user //! #[test] //! # fn _ignored() {} //! # pub //! fn name_is_correct() { //! // Setup our mocked reader and writer //! let args = vec!["ignored".to_string()]; //! let mut reader = MockReader(Some("John Smith".to_string())); //! let mut writer = MockWriter(String::new()); //! //! // Run the function we're testing //! let name = get_name(&args, &mut reader, &mut writer); //! //! // Verify that we got the right name //! assert_eq!("John Smith", name); //! assert!(!writer.0.is_empty()); //! } //! } //! ``` //! //! Cool, now we have a simple unit test for our program. We can now verify //! that it works automatically in our build pipelines, and we can be sure that //! future improvements to our program won't break it! //! //! Speaking of future improvements, our users love the program! They keep //! asking for more and more features to be added to it, though. There's a huge //! group of users requesting that we give them control over the output //! format, plus some users asking to be able to send these messages to people //! on the internet! Not only that, but some people want the network requests //! to be done via HTTPS and others want to use TCP. Woah, how in the world do //! we configure our application to be able to do all this, yet still have the //! ability to write unit tests for everything and understand what's going on? //! //! This is where dependency injection comes in. There is no way we could //! possibly manage all the different ways of writing outputs, reading inputs, //! configuring the greetings, and so on entirely on our own without our code //! becoming huge and complex. We would end up with a tangled web of //! dependencies between all the parts of our application, and it would quickly //! become unmaintainable. Instead, let's rely on a container to manage our //! dependencies for us so we don't need to think about that at all anymore. //! //! ``` //! use runtime_injector::{ //! interface, Arg, Injector, InjectorBuilder, IntoSingleton, Service, Svc, //! TypedProvider, WithArg, //! }; //! //! // Let's leave out the functions from our traits, we don't really need them //! // for this example. We need our trait to be a subtrait of `Service` so //! // that we can use type erasure in our container later on. Also, if our //! // services need to be thread-safe and we're using the "arc" feature for //! // runtime_injector, then `Service` will automatically require `Send` + //! // `Sync` for us //! trait OutputWriter: Service {} //! //! // We still want to be able to write to the console, but we need our output //! // formatter to make sure we correctly format our output //! struct ConsoleWriter(Svc<dyn OutputFormatter>); //! impl OutputWriter for ConsoleWriter {} //! //! // We also want to be able to send greetings across HTTPS //! struct HttpsWriter(Svc<dyn OutputFormatter>); //! impl OutputWriter for HttpsWriter {} //! //! // Finally, we need to support TCP as well //! struct TcpWriter(Svc<dyn OutputFormatter>); //! impl OutputWriter for TcpWriter {} //! //! // Also, we need to be able to mock this for testing //! #[cfg(test)] //! struct MockWriter(pub Arg<String>, Svc<dyn OutputFormatter>); //! #[cfg(test)] //! impl OutputWriter for MockWriter {} //! //! // We also need a way to format the messages //! trait OutputFormatter: Service {} //! //! // Our users want to be able to format them with custom formats! //! struct UserFormatter(pub Arg<String>); //! impl OutputFormatter for UserFormatter {} //! //! // Not all users want to use a custom format though, so we need a default //! #[derive(Default)] //! struct DefaultFormatter; //! impl OutputFormatter for DefaultFormatter {} //! //! // Now let's bring over our reader implementations //! trait InputReader: Service {} //! //! #[derive(Default)] //! struct ConsoleReader; //! impl InputReader for ConsoleReader {} //! //! #[cfg(test)] //! struct MockReader(pub Arg<Option<String>>); //! #[cfg(test)] //! impl InputReader for MockReader {} //! //! // Let's create an enum to help us configure our output writer too //! #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] //! enum OutputType { //! Console, //! Https, //! Tcp, //! } //! //! // Since we'll be relying on dependency injection, we need to provide our //! // container a little more information by declaring our interfaces. First, //! // we have three implementations of `OutputWriter` //! interface! { //! dyn OutputWriter = [ //! ConsoleWriter, //! HttpsWriter, //! TcpWriter, //! #[cfg(test)] //! MockWriter //! ] //! } //! //! // We also have two implementations of `OutputFormatter` //! interface! { //! dyn OutputFormatter = [ //! UserFormatter, //! DefaultFormatter //! ] //! } //! //! // Finally, we have two implementations of `InputReader` as well //! interface! { //! dyn InputReader = [ //! ConsoleReader, //! #[cfg(test)] //! MockReader //! ] //! } //! //! // We'll make a function to help us configure everything based on the //! // configuration settings our user gave us //! fn configure_services( //! user_format: Option<String>, //! output_type: OutputType, //! ) -> InjectorBuilder { //! // Now how do we manage all these possible implementations? Let's rely //! // on a container to manage them for us! //! let mut builder = Injector::builder(); //! //! // Let's configure everything without worrying about the testing //! // environment. We want to configure our code to use the console reader //! // for reading input since we don't support any other input readers //! builder.provide( //! ConsoleReader::default //! .singleton() //! .with_interface::<dyn InputReader>(), //! ); //! //! // We want to determine the formatter based on whether the user //! // provided a format to us //! if let Some(user_format) = user_format { //! // Let's register the user formatter as our output formatter since //! // the user gave us a custom format to use //! builder.provide( //! UserFormatter //! .singleton() //! .with_interface::<dyn OutputFormatter>(), //! ); //! //! // We want to also pass the user's custom format to our service //! builder.with_arg::<UserFormatter, _>(user_format); //! } else { //! // The user didn't give us a custom format, so we'll use the //! // default output formatter instead //! builder.provide( //! DefaultFormatter::default //! .singleton() //! .with_interface::<dyn OutputFormatter>(), //! ); //! } //! //! // Finally, we need to decide how we're going to send our greetings to //! // people. We can use our helper enum for that here //! match output_type { //! OutputType::Console => { //! builder.provide( //! ConsoleWriter //! .singleton() //! .with_interface::<dyn OutputWriter>(), //! ); //! } //! OutputType::Https => { //! builder.provide( //! HttpsWriter //! .singleton() //! .with_interface::<dyn OutputWriter>(), //! ); //! } //! OutputType::Tcp => { //! builder.provide( //! TcpWriter.singleton().with_interface::<dyn OutputWriter>(), //! ); //! } //! } //! //! // Let's return our injector builder now //! builder //! } //! //! fn main() { //! // We want the user to be able to configure the application here. //! // Normally, we'd use something like clap for this, but for the sake of //! // the example, we'll just hardcode the config //! let user_format = Some("Hello! Have a great day.".to_string()); //! let output_type = OutputType::Console; //! //! // With this, we have enough information to configure everything //! let builder = configure_services(user_format, output_type); //! //! // Finally, we just need to construct our services so we can use them //! let injector = builder.build(); //! let reader: Svc<dyn InputReader> = injector.get().unwrap(); //! let writer: Svc<dyn OutputWriter> = injector.get().unwrap(); //! //! // Now we can write our application logic! //! // ... //! } //! //! // Let's not forget about unit tests! //! #[cfg(test)] //! mod tests { //! use super::*; //! use runtime_injector::{define_module, Injector, IntoSingleton, Svc}; //! //! #[test] //! fn console_output_is_formatted_before_being_written() { //! // Let's make a custom module for testing just the console writer //! let module = define_module! { //! interfaces = { //! dyn OutputFormatter = [ //! DefaultFormatter::default.singleton(), //! ], //! }, //! services = [ //! // We won't need to put this behind an interface this time //! ConsoleWriter.singleton(), //! ], //! }; //! //! // We'll configure our injector now using the module we created //! let mut builder = Injector::builder(); //! builder.add_module(module); //! //! // Now we can test our console writer //! let injector = builder.build(); //! let writer: Svc<ConsoleWriter> = injector.get().unwrap(); //! //! // ... //! } //! } //! ```